def animate_scenes(sat_mlab_source, gsat_mlab_source, sat_pos_eci, lla_pos, eci_scene, groundtrack_scene): increment = 1 frame_play = 0 while frame_play <= sat_pos_eci.shape[0]: x, y, z = sat_pos_eci[frame_play, :] lat = attitude.normalize(np.rad2deg(lla_pos[frame_play, 0]), -90, 90) lon = attitude.normalize(np.rad2deg(lla_pos[frame_play, 1]), -180, 180) sat_mlab_source.set(x=x, y=y, z=z) gsat_mlab_source.set(x=lon, y=lat, z=0) frame_play += increment yield
def nu_solve(r, v, fpa, mu): """Single impulse manuever true anomaly nu = nu_solve(r,v,fpa) Inputs: - r - magnitude of position vector in km - v - magnitude of velocity vector in km - fpa - flight path angle in rad -pi/2 < fpa < pi/2 - mu - gravitational parameter km^3/sec^2 Outputs: - nu - true anomaly of body in rad 0 < nu < 2*pi Dependencies: - none Author: - Shankar Kulumani 10 Oct 2012 - Shankar Kulumani 18 November 2017 - in Python References - AAE532 notes LSN 18 - AAE532_PS7.pdf """ num = (r * v**2 / mu) * np.cos(fpa) * np.sin(fpa) den = (r * v**2 / mu) * np.cos(fpa)**2 - 1 nu = np.arctan2(num, den) nu = attitude.normalize(nu, 0, 2 * np.pi) return nu
def nu_solve(r, v, fpa, mu): """Single impulse manuever true anomaly nu = nu_solve(r,v,fpa) Inputs: - r - magnitude of position vector in km - v - magnitude of velocity vector in km - fpa - flight path angle in rad -pi/2 < fpa < pi/2 - mu - gravitational parameter km^3/sec^2 Outputs: - nu - true anomaly of body in rad 0 < nu < 2*pi Dependencies: - none Author: - Shankar Kulumani 10 Oct 2012 - Shankar Kulumani 18 November 2017 - in Python References - AAE532 notes LSN 18 - AAE532_PS7.pdf """ num = (r * v**2 / mu) * np.cos(fpa) * np.sin(fpa) den = (r * v**2 / mu) * np.cos(fpa)**2 - 1 nu = np.arctan2(num, den) nu = attitude.normalize(nu, 0, 2*np.pi) return nu
def asteroid_epoch(ast_flag): """ This holds the orbital elements for the asteroids as taken from JPL Return the state at the JD epoch for use in a propogate function """ if ast_flag == 0: # 2008 EV5 a = 0.9582899238313918 ecc = 0.08348599378460778 p = a * (1 - ecc**2) inc = np.deg2rad(7.436787362690259) raan = np.deg2rad(93.39122898787916) argp = np.deg2rad(234.8245876826614) M = np.deg2rad(3.409187469072454) JD_epoch = 2457800.5 elif ast_flag == 1: # Itokawa a = 1.324163617639197 ecc = 0.28011765678781 p = a * (1 - ecc**2) inc = np.deg2rad(1.62145641293925) raan = np.deg2rad(69.07992986350325) argp = np.deg2rad(162.8034822691509) M = np.deg2rad(131.4340297670125) JD_epoch = 2457800.5 elif ast_flag == 2: # Bennu a = 1.126391026007489 ecc = 0.2037451112033579 p = a * (1 - ecc**2) inc = np.deg2rad(6.034939195483961) raan = np.deg2rad(2.060867837066797) argp = np.deg2rad(66.22306857848962) M = np.deg2rad(101.7039476994243) JD_epoch = 2455562.5 else: print("No such asteroid defined yet. Using Itokawa so nothing breaks instead") # Itokawa a = 1.324163617639197 ecc = 0.28011765678781 p = a * (1 - ecc**2) inc = np.deg2rad(1.62145641293925) raan = np.deg2rad(69.07992986350325) argp = np.deg2rad(162.8034822691509) M = np.deg2rad(131.4340297670125) JD_epoch = 2457800.5 # convert M to nu for later and output the coe E, nu, count = kepler_eq_E(M, ecc) epoch = (p, ecc, inc, raan, argp, attitude.normalize(nu, 0, 2 * np.pi), JD_epoch) return epoch
def draw_groundtrack_sat(sat_lla, scene): lat = np.rad2deg(sat_lla[0]) lon = attitude.normalize(np.rad2deg(sat_lla[1]), -180, 180) alt = sat_lla[2] sat = mlab.points3d(lon, lat, 0, 1, figure=scene.mayavi_scene, scale_factor=10, color=(1, 0, 0)) return sat
def gstlst(jd, site_lon): """This program calculates GST and LST given the Julian Day and site longitude. Author: Shankar Kulumani GWU 18 Jun 2017 Inputs: jd : float Julian Day sitlon : site longitude (radians) Outputs: gst - Greenwich Sidereal Time lst - Local Sidereal Time Constants: None Coupling: Modifications: 18 Jun 2017 - use algorithm 15 from Vallado References: Astro 321 PREDICT Vallado Algorithm 15 """ deg2rad = np.pi / 180 hour2sec = 3600 sec2deg = 15 / 3600 Tut1 = (jd - 2451545.0) / 36525 gst = (- 6.2e-6 * Tut1 * Tut1 * Tut1 + 0.093104 * Tut1 * Tut1 + (876600.0 * 3600.0 + 8640184.812866) * Tut1 + 67310.54841) gst = (gst % 86400) * sec2deg gst = attitude.normalize(gst * deg2rad, 0, 2 * np.pi) lst = attitude.normalize(gst[0] + site_lon, 0, 2 * np.pi) return gst[0], lst[0]
class TestECEF2ECI(): """Example pg.106 in BMW """ lon = np.deg2rad(-57.296) lat = 0 alt = 6.378 # kilometer above equator date = (1970, 1, 2, 6, 0, 0) jd, _ = time.date2jd(date[0], date[1], date[2], date[3], date[4], date[5]) gst0_exp = 1.749333 gst_exp = attitude.normalize(9.6245, 0, 2 * np.pi) lst_exp = attitude.normalize(8.6245, 0, 2 * np.pi) eci_exp = np.array([-0.697 * 6378.137, 0.718 * 6378.137, 0]) gst0 = time.gsttime0(date[0]) gst, lst = time.gstlst(jd, lon) eci = geodetic.site2eci(lat, alt, lst) ecef = geodetic.lla2ecef(lat, lon, alt) Reci2ecef = transform.dcm_eci2ecef(jd) eci_from_ecef = Reci2ecef.T.dot(ecef) def test_gst0(self): np.testing.assert_allclose(self.gst0, self.gst0_exp, rtol=1e-4) def test_gst(self): np.testing.assert_allclose(self.gst, self.gst_exp, rtol=1e-4) def test_lst(self): np.testing.assert_allclose(self.lst, self.lst_exp, rtol=1e-3) def test_eci(self): np.testing.assert_allclose(self.eci, self.eci_exp, rtol=1e-2) def test_eci_from_ecef(self): np.testing.assert_allclose(self.eci_from_ecef, self.eci_exp, rtol=1e-2)
def gsttime0(yr): """This function finds the Greenwich Sidereal time at the beginning of a year. This formula is derived from the Astronomical Almanac and is good only for 0 hr UT, 1 Jan of a year. Algorithm : Find the Julian Date Ref 4713 BC Perform expansion calculation to obtain the answer Check the answer for the correct quadrant and size Author : Capt Dave Vallado USAFA/DFAS 719-472-4109 12 Feb 1989 In Ada : Dr Ron Lisowski USAFA/DFAS 719-472-4110 17 May 1995 In MatLab : LtCol Thomas Yoder USAFA/DFAS 719-333-4110 Spring 2000 Remove Global : Shankar Kulumani 7 Dec 2014 In Python : Shankar Kulumani GWU 630-336-6257 10 Jun 2017 Inputs : Yr - Year 1988, 1989, etc. OutPuts : GST0 - Returned Greenwich Sidereal Time 0 to 2Pi rad Locals : JD - Julian Date days from 4713 B.C. Tu - Julian Centuries from 1 Jan 2000 Constants : TwoPI Two times Pi (DFASmath.m constant) Coupling : mod MatLab modulus function References : 1989 Astronomical Almanac pg. B6 Escobal pg. 18 - 21 Explanatory Supplement pg. 73-75 Kaplan pg. 330-332 BMW pg. 103-104 """ TwoPI = 2 * np.pi JD = (367.0 * yr - np.fix(7.0 * (yr + np.fix(10.0 / 12.0)) * 0.25) + np.fix(275.0 / 9.0) + 1721014.5) Tu = (np.fix(JD) + 0.5 - 2451545.0) / 36525.0 GST0 = 1.753368559 + 628.3319705 * Tu + 6.770708127E-06 * Tu * Tu GST0 = attitude.normalize(GST0, 0, 2 * np.pi) return GST0
def asteroid_coe(JD_curr, ast_flag): """ Output the current COE for the chosen asteroid """ # load the asteroid COE at the epoch (p, ecc, inc, raan, argp, nu_0, JD_epoch) = asteroid_epoch(ast_flag) # compute the delta t delta_t = (JD_curr - JD_epoch) * 86400 mu = 1.32712440018e20 # m^3 / s^2 mu = 1 / 149597870700**3 * mu # au^3 / sec^2 # propogate to the current JD_curr (E_f, M_f, nu_f) = tof_delta_t(p, ecc, mu, nu_0, delta_t) # output the current COE coe = (p, ecc, inc, raan, argp, attitude.normalize(nu_f, 0, 2 * np.pi)) return coe
def rvfpa2orbit_el(mag_r, mag_v, fpa, mu): """Converts R,V, and FPA to orbital elements a, p, ecc, nu = rvfpa2orbit_el(mag_r, mag_v, fpa, mu) Inputs: - List all inputs into function Outputs: - List/describe outputs of function Dependencies: - list dependent m files required for function Author: - Shankar Kulumani 7 Oct 2012 - Shankar Kulumani 5 Nov 2012 - added ascending/descending logic to true anomaly - Shankar Kulumani 16 November 2017 - move to python References - AAE532_PS6.pdf """ # calculate semi major axis using energy relationship a = -mu / (mag_v**2 / 2 - mu / mag_r) / 2 sme = -mu / (2 * a) # find semi latus rectum and eccentricity mag_h = mag_r * mag_v * np.cos(fpa) p = mag_h**2 / mu ecc = np.sqrt(-p / a + 1) # find true anomaly using conic equation nu = np.arccos((p / mag_r - 1) / ecc) if fpa < 0: nu = - nu nu = attitude.normalize(nu, 0, 2 * np.pi)[0] # orbit_el(p,ecc,0,0,0,nu,mu,'true') return a, p, ecc, nu
def ecef2lla(ecef, r=6378.137, ee=8.1819190842622e-2): """Converts a ECEF vector to the equivalent Lat, longitude and Altitude above the reference ellipsoid """ twopi = 2 * np.pi tol = 1e-6 norm_vec = np.linalg.norm(ecef) temp = np.sqrt(ecef[0]**2 + ecef[1]**2) if temp < tol: rtasc = np.sign(ecef[2]) * np.pi * 0.5 else: rtasc = np.arctan2(ecef[1], ecef[0]) lon = rtasc lon = attitude.normalize(lon, 0, 2 * np.pi) decl = np.arcsin(ecef[2] / norm_vec) latgd = decl # iterate to find geodetic latitude i = 1 olddelta = latgd + 10.0 while np.absolute(olddelta - latgd) >= tol and i < 10: oldelta = latgd sintemp = np.sin(latgd) c = r / np.sqrt(1.0 - ee**2 * sintemp**2) latgd = np.arctan2(ecef[2] + c * ee**2 * sintemp, temp) i = i + 1 # calculate the height if (np.pi / 2 - np.absolute(latgd)) > np.pi / 180: hellp = temp / np.cos(latgd) - c else: s = c * (1 - ee**2) hellp = ecef[2] / np.sin(latgd) - s latgc = gd2gc(latgd, ee**2) return latgc, latgd, lon, hellp
def rvfpa2orbit_el(mag_r, mag_v, fpa, mu): """Converts R,V, and FPA to orbital elements a, p, ecc, nu = rvfpa2orbit_el(mag_r, mag_v, fpa, mu) Inputs: - List all inputs into function Outputs: - List/describe outputs of function Dependencies: - list dependent m files required for function Author: - Shankar Kulumani 7 Oct 2012 - Shankar Kulumani 5 Nov 2012 - added ascending/descending logic to true anomaly - Shankar Kulumani 16 November 2017 - move to python References - AAE532_PS6.pdf """ # calculate semi major axis using energy relationship a = -mu / (mag_v**2 / 2 - mu / mag_r) / 2 sme = -mu / (2 * a) # find semi latus rectum and eccentricity mag_h = mag_r * mag_v * np.cos(fpa) p = mag_h**2 / mu ecc = np.sqrt(-p / a + 1) # find true anomaly using conic equation nu = np.arccos((p / mag_r - 1) / ecc) if fpa < 0: nu = -nu nu = attitude.normalize(nu, 0, 2 * np.pi)[0] # orbit_el(p,ecc,0,0,0,nu,mu,'true') return a, p, ecc, nu
def ecef2lla(ecef, r=6378.137, ee=8.1819190842622e-2): """Converts a ECEF vector to the equivalent Lat, longitude and Altitude above the reference ellipsoid """ twopi = 2 * np.pi tol = 1e-6 norm_vec = np.linalg.norm(ecef) temp = np.sqrt(ecef[0]**2 + ecef[1]**2) if temp < tol: rtasc = np.sign(ecef[2]) * np.pi * 0.5 else: rtasc = np.arctan2(ecef[1], ecef[0]) lon = rtasc lon = attitude.normalize(lon, 0, 2 * np.pi) decl = np.arcsin(ecef[2] / norm_vec) latgd = decl # iterate to find geodetic latitude i = 1 olddelta = latgd + 10.0 while np.absolute(olddelta - latgd) >= tol and i < 10: oldelta = latgd sintemp = np.sin(latgd) c = r / np.sqrt(1.0 - ee**2 * sintemp**2) latgd = np.arctan2(ecef[2] + c * ee**2 * sintemp, temp) i = i + 1 # calculate the height if (np.pi / 2 - np.absolute(latgd)) > np.pi / 180: hellp = temp / np.cos(latgd) - c else: s = c * (1 - ee**2) hellp = ecef[2] / np.sin(latgd) - s latgc = gd2gc(latgd, ee**2) return latgc, latgd, lon, hellp
def rhoazel(sat_eci, site_eci, site_lat, site_lst): """ This function calculates the topcentric range,azimuth and elevation from the site vector and satellite position vector. Author: C2C Shankar Kulumani USAFA/CS-19 719-333-4741 Inputs: sat_eci - satellite ECI position vector (km) site_eci - site ECI position vector (km) site_lat - site geodetic latitude (radians) site_lst - site local sidereal time (radians) Outputs: rho - range (km) az - asimuth (radians) el - elevation (radians) Globals: None Constants: None Coupling: References: Astro 321 Predict LSN 22 """ site2sat_eci = sat_eci - site_eci site2sat_sez = attitude.rot3(-site_lst).dot(site2sat_eci) site2sat_sez = attitude.rot2(-(np.pi / 2 - site_lat)).dot(site2sat_sez) rho = np.linalg.norm(site2sat_sez) el = np.arcsin(site2sat_sez[2] / rho) az = attitude.normalize(np.arctan2(site2sat_sez[1], -site2sat_sez[0]), 0, 2 * np.pi)[0] return rho, az, el
def rhoazel(sat_eci, site_eci, site_lat, site_lst): """ This function calculates the topcentric range,azimuth and elevation from the site vector and satellite position vector. Author: C2C Shankar Kulumani USAFA/CS-19 719-333-4741 Inputs: sat_eci - satellite ECI position vector (km) site_eci - site ECI position vector (km) site_lat - site geodetic latitude (radians) site_lst - site local sidereal time (radians) Outputs: rho - range (km) az - asimuth (radians) el - elevation (radians) Globals: None Constants: None Coupling: References: Astro 321 Predict LSN 22 """ site2sat_eci = sat_eci - site_eci site2sat_sez = attitude.rot3(-site_lst).dot(site2sat_eci) site2sat_sez = attitude.rot2(-(np.pi / 2 - site_lat)).dot(site2sat_sez) rho = np.linalg.norm(site2sat_sez) el = np.arcsin(site2sat_sez[2] / rho) az = attitude.normalize(np.arctan2(site2sat_sez[1], -site2sat_sez[0]), 0, 2 * np.pi)[0] return rho, az, el
def kepler_eq_E(M_in, ecc_in): """Solve Kepler's Equation for all orbit types (E, nu, count) = kepler_eq_E(M, ecc) Purpose: - This function solves Kepler's equation for eccentric anomaly given a mean anomaly using a newton-rapson method. - Will work for elliptical/parabolic/hyperbolic orbits Inputs: - M - mean anomaly in rad -2*pi < M < 2*pi - ecc - eccentricity 0 < ecc < inf Outputs: - E - eccentric anomaly in rad 0 < E < 2*pi - nu - true anomaly in rad 0 < nu < 2*pi - count - number of iterations to converge Dependencies: - numpy - everything needs numpy - kinematics.attitude.normalize - normalize an angle Author: - Shankar Kulumani 15 Sept 2012 - rewritten from code from USAFA - solve for elliptical orbits add others later - Shankar Kulumani 29 Sept 2012 - added parabolic/hyperbolic functionality - Shankar Kulumani 7 Dec 2014 - added loop for vector inputs - Shankar Kulumani 2 Dec 2016 - converted to python and removed the vector inputs References - USAFA Astro 321 LSN 24-25 - Vallado 3rd Ed pg 72 """ tol = 1e-9 max_iter = 50 E_out = [] nu_out = [] count_out = [] if not hasattr(M_in, "__iter__"): M_in = [M_in] if not hasattr(ecc_in, "__iter__"): ecc_in = [ecc_in] for M, ecc in zip(M_in, ecc_in): # eccentricity check """ HYPERBOLIC ORBIT """ if ecc - 1.0 > tol: # eccentricity logic # initial guess if ecc < 1.6: # initial guess logic if M < 0.0 and (M > -np.pi or M > np.pi): E_0 = M - ecc else: E_0 = M + ecc else: if ecc < 3.6 and np.absolute(M) > np.pi: E_0 = M - np.sign(M) * ecc else: E_0 = M / (ecc - 1.0) # netwon's method iteration to find hyperbolic anomaly count = 1 E_1 = E_0 + ((M - ecc * np.sinh(E_0) + E_0) / (ecc * np.cosh(E_0) - 1.0)) while ((np.absolute(E_1 - E_0) > tol) and (count <= max_iter)): E_0 = E_1 E_1 = E_0 + ((M - ecc * np.sinh(E_0) + E_0) / (ecc * np.cosh(E_0) - 1.0)) count = count + 1 E = E_0 # find true anomaly sinv = -(np.sqrt(ecc * ecc - 1.0) * np.sinh(E_1)) / \ (1.0 - ecc * np.cosh(E_1)) cosv = (np.cosh(E_1) - ecc) / (1.0 - ecc * np.cosh(E_1)) nu = np.arctan2(sinv, cosv) else: """ PARABOLIC """ if np.absolute(ecc - 1.0) < tol: # parabolic logic count = 1 S = 0.5 * (np.pi / 2 - np.arctan(1.5 * M)) W = np.arctan(np.tan(S)**(1.0 / 3.0)) E = 2.0 * 1.0 / np.tan(2.0 * W) nu = 2.0 * np.arctan(E) else: """ ELLIPTICAl """ if ecc > tol: # elliptical logic # determine intial guess for iteration if M > -np.pi and (M < 0 or M > np.pi): E_0 = M - ecc else: E_0 = M + ecc # newton's method iteration to find eccentric anomaly count = 1 E_1 = E_0 + (M - E_0 + ecc * np.sin(E_0)) / \ (1.0 - ecc * np.cos(E_0)) while ((np.absolute(E_1 - E_0) > tol) and (count <= max_iter)): count = count + 1 E_0 = E_1 E_1 = E_0 + (M - E_0 + ecc * np.sin(E_0)) / \ (1.0 - ecc * np.cos(E_0)) E = E_0 # find true anomaly sinv = (np.sqrt(1.0 - ecc * ecc) * np.sin(E_1)) / \ (1.0 - ecc * np.cos(E_1)) cosv = (np.cos(E_1) - ecc) / (1.0 - ecc * np.cos(E_1)) nu = np.arctan2(sinv, cosv) else: """ CIRCULAR """ # -------------------- circular ------------------- count = 0 nu = M E = M E_out.append(E) nu_out.append(attitude.normalize(nu, 0, 2 * np.pi)) count_out.append(count) return (np.squeeze(E_out), np.squeeze(nu_out), np.squeeze(count_out))
def nu2anom(nu, ecc): """Calculates the eccentric and mean anomaly given eccentricity and true anomaly ( E, M ) = ecc_anomaly(nu,ecc) Inputs: - nu - true anomaly in rad -2*pi < nu < 2*pi - ecc - eccentricity of orbit 0 < ecc < inf Outputs: - E - (elliptical/parabolic/hyperbolic) eccentric anomaly in rad 0 < E < 2*pi - M - mean anomaly in rad 0 < M < 2*pi Dependencies: - numpy - everything is dependent on numpy - kinematics.attitude.normalize - normalize angles Notes ----- This function is valid for all orbit types. Author: - Shankar Kulumani 20 Nov 2017 - only now realized I already implemented other orbit types - Shankar Kulumani 5 Dec 2016 - Convert to python - Shankar Kulumani 15 Sept 2012 - modified from USAFA code and notes from AAE532 - only elliptical case will add other later - Shankar Kulumani 17 Sept 2012 - added rev check to reduce angle btwn 0 and 2*pi References - AAE532 notes - Vallado 3rd Ed """ small = 1e-9 if ecc <= small: # circular E = nu M = nu elif small < ecc and ecc <= 1 - small: # elliptical sine = (np.sqrt(1.0 - ecc * ecc) * np.sin(nu)) / \ (1.0 + ecc * np.cos(nu)) cose = (ecc + np.cos(nu)) / (1.0 + ecc * np.cos(nu)) E = np.arctan2(sine, cose) M = E - ecc * np.sin(E) E = attitude.normalize(E, 0, 2 * np.pi) M = attitude.normalize(M, 0, 2 * np.pi) elif np.absolute(ecc - 1) <= small: # parabolic B = np.tan(nu / 2) E = B M = B + 1.0 / 3 * B**3 # TODO: Need to check if I need to do quadrant checks for parabolic or hyperbolic cases # E = revcheck(E); # M = revcheck(M); elif ecc > 1 + small: # hyperbolic sine = (np.sqrt(ecc**2 - 1) * np.sin(nu)) / \ (1.0 + ecc * np.cos(nu)) H = np.arcsinh(sine) E = H M = ecc * np.sinh(H) - H # E = revcheck(E); # M = revcheck(M); else: print("Eccentricity is out of bounds 0 < ecc < inf") return (E, M)
def tle_update(self, jd_span, mu=398600.5): """Update the state of satellite given a JD time span This procedure uses the method of general perturbations to update classical elements from epoch to a future time for inclined elliptical orbits. It includes the effects due to first order secular rates (second order for mean motion) caused by drag and J2. Author: C2C Shankar Kulumani USAFA/CS-19 719-333-4741 Inputs: deltat - elapsed time since epoch (sec) n0 - mean motion at epoch (rad/sec) ndot2 - mean motion rate divided by 2 (rad/sec^2) ecc0 - eccentricity at epoch (unitless) eccdot - eccentricity rate (1/sec) raan0 - RAAN at epoch (rad) raandot - RAAN rate (rad/sec) argp0 - argument of periapsis at epoch (rad) argdot - argument of periapsis rate (rad/sec) mean0 - mean anomaly at epoch Outputs: n - mean motion at epoch + deltat (rad/sec) ecc - eccentricity at epoch + deltat (unitless) raan - RAAN at epoch + deltat (rad) argp - argument of periapsis at epoch + deltat (rad) nu - true anomaly at epoch + deltat (rad) Globals: None Constants: None Coupling: newton.m References: Astro 321 PREDICT LSN 24-25 """ deltat = (jd_span - self.epoch_jd) * day2sec # epoch orbit parameters n0 = self.n0 ecc0 = self.ecc0 ndot2 = self.ndot2 nddot6 = self.nddot6 raan0 = self.raan0 argp0 = self.argp0 mean0 = self.mean0 inc0 = self.inc0 adot = self.adot eccdot = self.eccdot raandot = self.raandot argpdot = self.argpdot _, nu0, _ = kepler.kepler_eq_E(mean0, ecc0) a0 = kepler.n2a(n0, mu) M0, E0 = kepler.nu2anom(nu0, ecc0) # propogated elements a1 = a0 + adot * deltat # either use this or compute a1 from n1 instead ecc1 = ecc0 + eccdot * deltat raan1 = raan0 + raandot * deltat argp1 = argp0 + argpdot * deltat inc1 = inc0 * np.ones_like(ecc1) n1 = n0 + ndot2 * 2 * deltat # a1 = kepler.n2a(n1, mu) p1 = kepler.semilatus_rectum(a1, ecc1) M1 = M0 + n0 * deltat+ ndot2 *deltat**2 + nddot6 *deltat**3 M1 = attitude.normalize(M1, 0, 2 * np.pi) E1, nu1, _ = kepler.kepler_eq_E(M1, ecc1) # convert to vectors r_eci, v_eci, _, _ = kepler.coe2rv(p1, ecc1, inc1, raan1, argp1, nu1, mu) # save all of the variables to the object self.jd_span = jd_span self.coe = COE(n=n1, ecc=ecc1, raan=raan1, argp=argp1, mean=M1, E=E1, nu=nu1, a=a1, p=p1, inc=inc1) self.r_eci = r_eci self.v_eci = v_eci return 0
def minenergy(r1, r2, r_body, mu_body, direction): """Lambert minimum energy ellipse ( v1 v2 a p ecc ) = lambert_minenergy(r1,r2,r_body,mu_body) Purpose: - Finds the minimum energy solution to lamberts problems given two position vectors. Inputs: - r1 - initial position vector in km (1x3 or 3x1) - r2 - final position vector in km (1x3 or 3x1) - r_body - radius of central body in km (check for collision) - mu_body - gravitational parameter of central body in km^3/sec^2 - direction - 'long' or 'short' direction of travel Outputs: - v1 - initial velocity vector in km/sec - v2 - final velocity vector in km/sec - a - semimajor axis of transfer ellipse in km - p - semiparameter of transfer ellipse in km - ecc - eccentricity of transfer ellipse - F - position vector of vacant focus in km Dependencies: - fg_nu - calculates velocity vectors using f and g functions - crash_check - check for crash into central body Author: - Shankar Kulumani 5 Nov 2012 - Shankar Kulumani 6 Nov 2012 - added vacant focus calculation - Shankar Kulumani 14 Nov 2012 - added crash check - Shankar Kulumani 13 Dec 2017 - now in python References - AAE532 Notes LSN 30-33 - Vallado 3rd edition """ # find transfer angle mag_r1 = np.linalg.norm(r1) mag_r2 = np.linalg.norm(r2) cos_nu = np.dot(r1,r2)/(mag_r1*mag_r2) if direction == 'short': sin_nu = np.linalg.norm(np.cross(r1,r2))/(mag_r1*mag_r2) elif direction == 'long': sin_nu = -np.linalg.norm(np.cross(r1,r2))/(mag_r1*mag_r2) delta_nu = np.arctan2(sin_nu,cos_nu) # TODO Check on angle range for delta nu delta_nu = attitude.normalize(delta_nu, 0, 2* np.pi) # Intermediate parameters # c = sqrt(mag_r1^2+mag_r2^2-2*mag_r1*mag_r2*cos(delta_nu)); % Chord c_vec = r2-r1 c = np.linalg.norm(c_vec) s = (mag_r1+mag_r2+c)/2 # Semiperimeter a = s/2 # p = mag_r1*mag_r2/c*(1-cos(delta_nu)); % Semiparameter? alpha = 2*np.arcsin(np.sqrt(s/(2*a))) beta = 2*np.arcsin(np.sqrt((s-c)/(2*a))) p = 4*a*(s-mag_r1)*(s-mag_r2)/c**2*(np.sin(1/2*(alpha + beta))**2) # Transfer orbit physical properties ecc = np.sqrt(1-2*p/s) # ecc = c/(2*a); # vacant focus F = (2*a-mag_r1)*c_vec/c + r1 # F = (2*a_m-norm(r1))*-c/norm(c) + r1 # find velocity at each point # V0 = (sqrt(mu*p)/(r0*r*sin(v)))*(R - (1 - r/p*(1-cos(v)))*R0); ( v1, v2, f, g, f_dot, g_dot ) = kepler.fg_velocity(r1,r2,delta_nu,p,mu_body) # Transfer orbit time B = 2*np.arcsin(np.sqrt((s-c)/s)) if delta_nu > np.pi: B = -B tof = np.sqrt(a**3/mu_body)*(np.pi - B + np.sin(B)) # Check for crash crash_check(r1,v1,r2,v2,mu_body,r_body) return v1, v2, tof, a, p, ecc
def rv2rhoazel(r_sat_eci, v_sat_eci, lat, lon, alt, jd): """ This function calculates the topcentric range,azimuth and elevation from the site vector and satellite position vector. Author: C2C Shankar Kulumani USAFA/CS-19 719-333-4741 Inputs: sat_eci - satellite ECI position vector (km) site_eci - site ECI position vector (km) site_lat - site geodetic latitude (radians) site_lst - site local sidereal time (radians) Outputs: rho - range (km) az - asimuth (radians) el - elevation (radians) Globals: None Constants: None Coupling: References: Astro 321 Predict LSN 22 Vallado Algorithm 27 """ small = constants.small halfpi = constants.halfpi # get site in ECEF r_site_ecef = lla2ecef(lat, lon, alt) # convert sat and site to ecef dcm_eci2ecef = transform.dcm_eci2ecef(jd) omega_earth = np.array([0, 0, constants.earth.omega]) r_sat_ecef = dcm_eci2ecef.dot(r_sat_eci) v_sat_ecef = dcm_eci2ecef.dot(v_sat_eci) - np.cross( omega_earth, r_sat_ecef) # find relative vector in ecef frame rho_ecef = r_sat_ecef - r_site_ecef drho_ecef = v_sat_ecef - np.zeros_like(v_sat_ecef) # site isn't moving rho = np.linalg.norm(rho_ecef) # convert to SEZ coordinate frame dcm_ecef2sez = attitude.rot2(np.pi / 2 - lat, 'r').dot(attitude.rot3(lon, 'r')) rho_sez = dcm_ecef2sez.dot(rho_ecef) drho_sez = dcm_ecef2sez.dot(drho_ecef) # find azimuth and eleveation temp = np.sqrt(rho_sez[0]**2 + rho_sez[1]**2) if temp < small: # directly over the north pole el = np.sign(rho_sez[2]) * halfpi # \pm 90 deg else: mag_rho_sez = np.linalg.norm(rho_sez) el = np.arcsin(rho_sez[2] / mag_rho_sez) if temp < small: az = np.arctan2(drho_sez[1], -drho_sez[0]) else: az = np.arctan2(rho_sez[1] / temp, -rho_sez[0] / temp) # range, azimuth and elevation rates drho = np.dot(rho_sez, drho_sez) / rho if np.absolute(temp**2) > small: daz = (drho_sez[0] * rho_sez[1] - drho_sez[1] * rho_sez[0]) / temp**2 else: daz = 0 if np.absolute(temp) > small: dele = (drho_sez[2] - drho * np.sin(el)) / temp else: dele = 0 az = attitude.normalize(az, 0, constants.twopi) return rho, az, el, drho, daz, dele
def planet_coe(JD_curr, planet_flag): r"""Orbital elements for any of the major solar system bodies Given the Julian date this function will output the approximate orbital elements for any of the solar system bodies. It uses an analytic approximation for their positions and propogates the orbital elements to the desired date. Parameters ---------- jd : float Julian date for the epoch planet_flag : int Number 0 to 8 for each of the planets (Pluto forever) Returns ------- coe : tuple p : float Semiparameter of orbit in AU ecc: float Eccentricity of orbit inc : float Inclination in radiands wrt to ecliptic raan : float Longitude/Right ascension of teh ascending node in radians argp : float Argument of perigee in radians nu : float True anomaly in radians r_ecliptic : (3,) ndarray Position wrt J2000 mean ecliptic (Earth's orbit around the sun) in kilometers defined relative to the solar system barycenter v_ecliptic : (3,) ndarray Velocity wrt J2000 mean ecliptic in kilometers per second defined relative to the solar system barycenter r_icrf : (3,) ndarray Position wrt J2000 equatorial/ICRF in kilometers and defined relative to teh solar system barycenter v_icrf : (3,) Velocity wrt J2000 equatorial/ICRF in kilometers per second defined relative to the solar system barycenter See Also -------- planet_approx : Actually has the data for the approximations Notes ----- The orbital elements are defined with respect to the mean ecliptic and equinox of J2000. The fundamental plane is the ecliptic, or the orbit of the Earth about the sun. The fundamental direction is aligned with the first point of Ares or the vernal equinox. The vectors are defined in both the ecliptic and equatorial system with respect to the solar system barycenter Author ------ Shankar Kulumani GWU [email protected] 23 June 2017 References ---------- .. [1] https://ssd.jpl.nasa.gov/?planet_pos .. [2] MEEUS, Jean H.. Astronomical Algorithms. Willmann-Bell, Incorporated, 1991. Examples -------- An example of how to use the function >>> planet_flag = 4 >>> coe = planet_coe(jd, planet_flag) """ # Find the JD centuries past J2000 epoch JD_J2000 = 2451545.0 T = (JD_curr - JD_J2000) / 36525 # load the elements and element rates for the planets (a0, adot, e0, edot, inc0, incdot, meanL0, meanLdot, lonperi0, lonperidot, raan0, raandot, b, c, f, s) = planet_approx(JD_curr, planet_flag) # compute the current elements at this JD a = a0 + adot * T ecc = e0 + edot * T inc = inc0 + incdot * T L = meanL0 + meanLdot * T lonperi = lonperi0 + lonperidot * T raan = raan0 + raandot * T # compute argp and M/v to complete the element set argp = lonperi - raan M = L - lonperi + b * T**2 + c * np.cos(f * T) + s * np.sin(f * T) # M = attitude.normalize(M, -180, 180) # solve kepler's equation to compute E and v E, nu, count = kepler.kepler_eq_E(np.deg2rad(M), ecc) argp = attitude.normalize(np.deg2rad(argp), 0, 2 * np.pi)[0] # package into a vector and output p = a * (1 - ecc**2) au2km = constants.au2km coe = COE(p=p*au2km, ecc=ecc, inc=np.deg2rad(inc), raan=np.deg2rad(raan), argp=argp, nu=nu) # convert to position and velocity vectors in J2000 ecliptic and J2000 # Earth equatorial (ECI) reference frame # need to convert distances to working units of kilometers r_ecliptic, v_ecliptic, r_pqw, v_pqw = kepler.coe2rv(coe.p, coe.ecc, coe.inc, coe.raan, coe.argp, coe.nu.item(), constants.sun.mu) # convert to the Earth J2000 frame (ECI) need to rotate by the obliquity of # the ecliptic Eclip2ECI = attitude.rot1(constants.obliquity) r_eci = Eclip2ECI.dot(r_ecliptic) v_eci = Eclip2ECI.dot(v_ecliptic) return coe, r_ecliptic, v_ecliptic, r_eci, v_eci
def test_vectorized_interior(self): angles = np.linspace(10, 50, 10) expected = angles actual = attitude.normalize(angles, 0, 180) np.testing.assert_allclose(actual, expected)
def minenergy(r1, r2, r_body, mu_body, direction): """Lambert minimum energy ellipse ( v1 v2 a p ecc ) = lambert_minenergy(r1,r2,r_body,mu_body) Purpose: - Finds the minimum energy solution to lamberts problems given two position vectors. Inputs: - r1 - initial position vector in km (1x3 or 3x1) - r2 - final position vector in km (1x3 or 3x1) - r_body - radius of central body in km (check for collision) - mu_body - gravitational parameter of central body in km^3/sec^2 - direction - 'long' or 'short' direction of travel Outputs: - v1 - initial velocity vector in km/sec - v2 - final velocity vector in km/sec - a - semimajor axis of transfer ellipse in km - p - semiparameter of transfer ellipse in km - ecc - eccentricity of transfer ellipse - F - position vector of vacant focus in km Dependencies: - fg_nu - calculates velocity vectors using f and g functions - crash_check - check for crash into central body Author: - Shankar Kulumani 5 Nov 2012 - Shankar Kulumani 6 Nov 2012 - added vacant focus calculation - Shankar Kulumani 14 Nov 2012 - added crash check - Shankar Kulumani 13 Dec 2017 - now in python References - AAE532 Notes LSN 30-33 - Vallado 3rd edition """ # find transfer angle mag_r1 = np.linalg.norm(r1) mag_r2 = np.linalg.norm(r2) cos_nu = np.dot(r1, r2) / (mag_r1 * mag_r2) if direction == 'short': sin_nu = np.linalg.norm(np.cross(r1, r2)) / (mag_r1 * mag_r2) elif direction == 'long': sin_nu = -np.linalg.norm(np.cross(r1, r2)) / (mag_r1 * mag_r2) delta_nu = np.arctan2(sin_nu, cos_nu) # TODO Check on angle range for delta nu delta_nu = attitude.normalize(delta_nu, 0, 2 * np.pi) # Intermediate parameters # c = sqrt(mag_r1^2+mag_r2^2-2*mag_r1*mag_r2*cos(delta_nu)); % Chord c_vec = r2 - r1 c = np.linalg.norm(c_vec) s = (mag_r1 + mag_r2 + c) / 2 # Semiperimeter a = s / 2 # p = mag_r1*mag_r2/c*(1-cos(delta_nu)); % Semiparameter? alpha = 2 * np.arcsin(np.sqrt(s / (2 * a))) beta = 2 * np.arcsin(np.sqrt((s - c) / (2 * a))) p = 4 * a * (s - mag_r1) * (s - mag_r2) / c**2 * (np.sin( 1 / 2 * (alpha + beta))**2) # Transfer orbit physical properties ecc = np.sqrt(1 - 2 * p / s) # ecc = c/(2*a); # vacant focus F = (2 * a - mag_r1) * c_vec / c + r1 # F = (2*a_m-norm(r1))*-c/norm(c) + r1 # find velocity at each point # V0 = (sqrt(mu*p)/(r0*r*sin(v)))*(R - (1 - r/p*(1-cos(v)))*R0); (v1, v2, f, g, f_dot, g_dot) = kepler.fg_velocity(r1, r2, delta_nu, p, mu_body) # Transfer orbit time B = 2 * np.arcsin(np.sqrt((s - c) / s)) if delta_nu > np.pi: B = -B tof = np.sqrt(a**3 / mu_body) * (np.pi - B + np.sin(B)) # Check for crash crash_check(r1, v1, r2, v2, mu_body, r_body) return v1, v2, tof, a, p, ecc
def test_upper_limit_circle(self): expected = -180 actual = attitude.normalize(180, -180, 180) np.testing.assert_almost_equal(actual, expected)
def test_past_lower_limit_circle(self): expected = 179 actual = attitude.normalize(-181, -180, 180) np.testing.assert_almost_equal(actual, expected)
def rv2rhoazel(r_sat_eci, v_sat_eci, lat, lon, alt, jd): """ This function calculates the topcentric range,azimuth and elevation from the site vector and satellite position vector. Author: C2C Shankar Kulumani USAFA/CS-19 719-333-4741 Inputs: sat_eci - satellite ECI position vector (km) site_eci - site ECI position vector (km) site_lat - site geodetic latitude (radians) site_lst - site local sidereal time (radians) Outputs: rho - range (km) az - asimuth (radians) el - elevation (radians) Globals: None Constants: None Coupling: References: Astro 321 Predict LSN 22 Vallado Algorithm 27 """ small = constants.small halfpi = constants.halfpi # get site in ECEF r_site_ecef = lla2ecef(lat, lon, alt) # convert sat and site to ecef dcm_eci2ecef = transform.dcm_eci2ecef(jd) omega_earth = np.array([0, 0, constants.earth.omega]) r_sat_ecef = dcm_eci2ecef.dot(r_sat_eci) v_sat_ecef = dcm_eci2ecef.dot( v_sat_eci) - np.cross(omega_earth, r_sat_ecef) # find relative vector in ecef frame rho_ecef = r_sat_ecef - r_site_ecef drho_ecef = v_sat_ecef - np.zeros_like(v_sat_ecef) # site isn't moving rho = np.linalg.norm(rho_ecef) # convert to SEZ coordinate frame dcm_ecef2sez = attitude.rot2( np.pi / 2 - lat, 'r').dot(attitude.rot3(lon, 'r')) rho_sez = dcm_ecef2sez.dot(rho_ecef) drho_sez = dcm_ecef2sez.dot(drho_ecef) # find azimuth and eleveation temp = np.sqrt(rho_sez[0]**2 + rho_sez[1]**2) if temp < small: # directly over the north pole el = np.sign(rho_sez[2]) * halfpi # \pm 90 deg else: mag_rho_sez = np.linalg.norm(rho_sez) el = np.arcsin(rho_sez[2] / mag_rho_sez) if temp < small: az = np.arctan2(drho_sez[1], - drho_sez[0]) else: az = np.arctan2(rho_sez[1] / temp, -rho_sez[0] / temp) # range, azimuth and elevation rates drho = np.dot(rho_sez, drho_sez) / rho if np.absolute(temp**2) > small: daz = (drho_sez[0] * rho_sez[1] - drho_sez[1] * rho_sez[0]) / temp**2 else: daz = 0 if np.absolute(temp) > small: dele = (drho_sez[2] - drho * np.sin(el)) / temp else: dele = 0 az = attitude.normalize(az, 0, constants.twopi) return rho, az, el, drho, daz, dele
def sun_earth_eci(jd): """This function calculates the Geocentric Equatorial position vector for the Sun given the Julian Date. This is the low precision formula and is valid for years from 1950 to 2050. Accuaracy of apparent coordinates is 0.01 degrees. Notice many of the calculations are performed in degrees, and are not changed until later. This is due to the fact that the Almanac uses degrees exclusively in their formulations. Algorithm : Calculate the several values needed to find the vector Be careful of quadrant checks Author : Capt Dave Vallado USAFA/DFAS 719-472-4109 25 Aug 1988 In Ada : Dr Ron Lisowski USAFA/DFAS 719-472-4110 17 May 1995 In MatLab : Dr Ron Lisowski USAFA/DFAS 719-333-4109 10 Oct 2001 In Python : Shankar Kulumani GWU 630-336-6257 19 Jun 2017 Inputs : JD - Julian Date days from 4713 B.C. Outputs : RSun - IJK Position vector of the Sun km RtAsc - Right Ascension rad Decl - Declination rad Locals : MeanLong - Mean Longitude MeanAnomaly - Mean anomaly N - Number of days from 1 Jan 2000 EclpLong - Ecliptic longitude Obliquity - Mean Obliquity of the Ecliptic Constants : Pi - TwoPI - InvRad - Radians per degree Coupling : References : 1996 Astronomical Almanac Pg. C24 http://aa.usno.navy.mil/faq/docs/SunApprox.php """ N = jd - 2451545.0 meanlong = 280.461 + 0.9856474 * N meanlong = attitude.normalize(meanlong, 0, 360)[0] meananomaly = 357.528 + 0.9856003 * N meananomaly = attitude.normalize(meananomaly * deg2rad, 0, twopi)[0] if meananomaly < 0: meananomaly = twopi + meananomaly eclplong = meanlong + 1.915 * \ np.sin(meananomaly) + 0.020 * np.sin(2 * meananomaly) obliquity = 23.439 - 0.0000004 * N meanlong = meanlong * deg2rad if meanlong < 0: meanlong = twopi + meanlong eclplong = eclplong * deg2rad obliquity = obliquity * deg2rad ra = np.arctan2(np.cos(obliquity) * np.sin(eclplong), np.cos(eclplong)) dec = np.arcsin(np.sin(obliquity) * np.sin(eclplong)) # equation of time eqtime = meanlong * rad2deg / 15 - ra * rad2deg / 15 # sun vector sun_dist = 1.00014 - 0.01671 * \ np.cos(meananomaly) - 0.00014 * np.cos(2 * meananomaly) semidiameter = 0.2666 / sun_dist # angular semidiamter in deg sun_eci = [ np.cos(eclplong) * sun_dist * au2km, np.cos(obliquity) * np.sin(eclplong) * sun_dist * au2km, np.sin(obliquity) * np.sin(eclplong) * sun_dist * au2km ] return np.squeeze(sun_eci), ra, dec
def tle_update(self, jd_span, mu=398600.5): """Update the state of satellite given a JD time span This procedure uses the method of general perturbations to update classical elements from epoch to a future time for inclined elliptical orbits. It includes the effects due to first order secular rates (second order for mean motion) caused by drag and J2. Author: C2C Shankar Kulumani USAFA/CS-19 719-333-4741 Inputs: deltat - elapsed time since epoch (sec) n0 - mean motion at epoch (rad/sec) ndot2 - mean motion rate divided by 2 (rad/sec^2) ecc0 - eccentricity at epoch (unitless) eccdot - eccentricity rate (1/sec) raan0 - RAAN at epoch (rad) raandot - RAAN rate (rad/sec) argp0 - argument of periapsis at epoch (rad) argdot - argument of periapsis rate (rad/sec) mean0 - mean anomaly at epoch Outputs: n - mean motion at epoch + deltat (rad/sec) ecc - eccentricity at epoch + deltat (unitless) raan - RAAN at epoch + deltat (rad) argp - argument of periapsis at epoch + deltat (rad) nu - true anomaly at epoch + deltat (rad) Globals: None Constants: None Coupling: newton.m References: Astro 321 PREDICT LSN 24-25 """ deltat = (jd_span - self.epoch_jd) * day2sec # epoch orbit parameters n0 = self.n0 ecc0 = self.ecc0 ndot2 = self.ndot2 nddot6 = self.nddot6 raan0 = self.raan0 argp0 = self.argp0 mean0 = self.mean0 inc0 = self.inc0 adot = self.adot eccdot = self.eccdot raandot = self.raandot argpdot = self.argpdot _, nu0, _ = kepler.kepler_eq_E(mean0, ecc0) a0 = kepler.n2a(n0, mu) M0, E0 = kepler.nu2anom(nu0, ecc0) # propogated elements a1 = a0 + adot * deltat # either use this or compute a1 from n1 instead ecc1 = ecc0 + eccdot * deltat raan1 = raan0 + raandot * deltat argp1 = argp0 + argpdot * deltat inc1 = inc0 * np.ones_like(ecc1) n1 = n0 + ndot2 * 2 * deltat # a1 = kepler.n2a(n1, mu) p1 = kepler.semilatus_rectum(a1, ecc1) M1 = M0 + n0 * deltat + ndot2 * deltat**2 + nddot6 * deltat**3 M1 = attitude.normalize(M1, 0, 2 * np.pi) E1, nu1, _ = kepler.kepler_eq_E(M1, ecc1) # convert to vectors r_eci, v_eci, _, _ = kepler.coe2rv(p1, ecc1, inc1, raan1, argp1, nu1, mu) # save all of the variables to the object self.jd_span = jd_span self.coe = COE(n=n1, ecc=ecc1, raan=raan1, argp=argp1, mean=M1, E=E1, nu=nu1, a=a1, p=p1, inc=inc1) self.r_eci = r_eci self.v_eci = v_eci return 0