def computeSEPandSPEangles(self, current_epoch, spacecraft_SPICE_ID): # get position of object w.r.t. Earth spacecraft_state_wrt_Earth, light_times = spice.spkez( spacecraft_SPICE_ID, current_epoch, "J2000", 'NONE', 399) # get position of Sun w.r.t. Earth sun_state_wrt_Earth, light_times = spice.spkez(10, current_epoch, "J2000", 'NONE', 399) Earth_state_wrt_spacecraft = [-x for x in spacecraft_state_wrt_Earth] sun_state_wrt_spacecraft = sun_state_wrt_Earth - spacecraft_state_wrt_Earth cosAngle_SEP = self.np.dot( sun_state_wrt_Earth[0:3], spacecraft_state_wrt_Earth[0:3]) / self.np.linalg.norm( sun_state_wrt_Earth[0:3]) / self.np.linalg.norm( spacecraft_state_wrt_Earth[0:3]) cosAngle_SPE = self.np.dot( sun_state_wrt_spacecraft[0:3], Earth_state_wrt_spacecraft[0:3]) / self.np.linalg.norm( sun_state_wrt_spacecraft[0:3]) / self.np.linalg.norm( Earth_state_wrt_spacecraft[0:3]) SEP_angle = self.np.arccos(cosAngle_SEP) * 180.0 / self.np.pi SPE_angle = self.np.arccos(cosAngle_SPE) * 180.0 / self.np.pi return SEP_angle, SPE_angle
def generateDistanceReport(self, output_file, ephemeris_file, body_SPICE_IDs=[]): header_row = "Gregorian_date_TDB, Julian_date_TDB, " for bodyID in body_SPICE_IDs: pass ephem_file_rows = self.ephemeris_file_reader.parseEMTGephemerisFile( ephemeris_file) for ephem_row in ephem_file_rows: # compute distance from the Sun spacecraft_distance_from_sun = np.sqrt( ephem_row.spacecraft_position_x**2 + ephem_row.spacecraft_position_y**2 + ephem_row.spacecraft_position_z**2) for bodyID in body_SPICE_IDs: # get the distance from the Sun to the body current_seconds_past_J2000 = spice.str2et( ephem_row.gregorian_date) body_state_ICRF, sun_body_light_times = spice.spkez( 10, current_seconds_past_J2000, 'J2000', 'NONE', bodyID) spacecraft_distance_from_body = np.sqrt( (ephem_row.spacecraft_position_x - body_state_ICRF[0])**2 + (ephem_row.spacecraft_position_y - body_state_ICRF[1])**2 + (ephem_row.spacecraft_position_z - body_state_ICRF[2])**2)
def rv(self, date, frame="ECLIPJ2000", corr="NONE", observer=10): """Position and velocity vectors. Parameters ---------- date : string, float, astropy Time, datetime Processed via `util.date2time`. frame : string The name of a SPICE reference frame. corr : string The SPICE abberation correction. observer : integer The NAIF ID of the observer when computing vectors. Returns ------- r : ndarray Position vector. [km] v : ndarray Velocity vector. [km/s] """ from .. import util N = util.date_len(date) if N > 0: rv = tuple(zip(*(self.rv(d) for d in date))) return np.array(rv[0]), np.array(rv[1]) et = core.date2et(date) # no light corrections, sun = 10 state, lt = spice.spkez(self.naifid, et, frame, corr, observer) return np.array(state[:3]), np.array(state[3:])
def print_state(epoch): et = sp.utc2et(epoch) print() print(epoch, et) sc_from_moon = sp.spkez(-10003001, et, 'j2000', 'none', 301)[0] print("Moon J2000", sc_from_moon) sc_from_earth = sp.spkez(-10003001, et, 'j2000', 'none', 399)[0] print("EME2000", sc_from_earth) moon_from_earth = sp.spkez(301, et, 'j2000', 'none', 399)[0] print("Moon from Earth:", moon_from_earth) moon_from_emb = sp.spkez(301, et, 'j2000', 'none', 3)[0] print("Moon from EMB:", moon_from_emb) earth_from_emb = sp.spkez(399, et, 'j2000', 'none', 3)[0] print("Earth from EMB:", earth_from_emb) sum = -earth_from_emb + moon_from_emb + sc_from_moon print("Sum: -earth_from_emb + moon_from_emb + sc_from_moon\n\t", sum) print("Delta:", sum - sc_from_earth)
def computeDataFrameGeometries(self, data_frames, greg_format_string, julian_format_string): for data_frame in data_frames: self.greg_format_string = greg_format_string self.julian_format_string = julian_format_string greg_split_string = self.greg_format_string.split(' ') data_frame.greg_time_system_string = [ i for i in greg_split_string if '::' in i ][0][2:] julian_split_string = self.julian_format_string.split(' ') data_frame.julian_time_system_string = [ i for i in julian_split_string if '::' in i ][0][2:] current_epoch = data_frame.start_epoch current_epoch = spice.str2et(current_epoch) stop_epoch = spice.str2et(data_frame.stop_epoch) time_remaining = stop_epoch - current_epoch while time_remaining >= 0.0: greg_date_string = spice.timout(current_epoch, self.greg_format_string) JD_date = spice.timout(current_epoch, self.julian_format_string) for body_pair in data_frame.body_pairs: # get target body state body_state, light_times = spice.spkez( spice.bodn2c(body_pair[1]), current_epoch, data_frame.frame, 'NONE', spice.bodn2c(body_pair[0])) distance_km = self.np.linalg.norm(body_state[0:3]) distance_AU = distance_km / self.AU data_frame.data[body_pair[0]][body_pair[1]].append([ greg_date_string, JD_date, body_state, distance_km, distance_AU ]) # compute solar geometries SEP_angle, SPE_angle = self.computeSEPandSPEangles( current_epoch, data_frame.spacecraft_SPICE_ID) data_frame.data['SEP'].append( [greg_date_string, JD_date, SEP_angle]) data_frame.data['SPE'].append( [greg_date_string, JD_date, SPE_angle]) # compute any eclipses self.computeCircleCircleOccultations(current_epoch, greg_date_string, JD_date, data_frame) if time_remaining == 0.0: break else: if time_remaining >= data_frame.time_step: current_epoch += data_frame.time_step time_remaining -= data_frame.time_step else: time_remaining -= stop_epoch - current_epoch current_epoch += stop_epoch - current_epoch
def state_spice(xfunc, tais, **kwargs): res = {} try: for tai in tais: et = sp.unitim(tai, 'tai', 'et') sta = sp.spkez(kwargs['target'], et, kwargs['ref'], kwargs['abcorr'], kwargs['observer'])[0] utc = sp.et2utc(et, 'isoc', 3) res[utc] = [xfunc(sta[0:3]), sta[3:6]] except sp.stypes.SpiceyError as ex: return ex.value else: return res
def locate_body_spice(self,JD, mu, AU): try: import spiceypy as spice except: print("PySpice not available") try: state,time = spice.spkez(self.SPICEID,(JD-2400000.5-51544.5)*86400.0,"eclipJ2000","NONE",10) r = state[0:3] v = state[3:6] except: print(sys.exc_info()[0]) #call Ryne's Kepler solver r, v = kepler(self.SMA * AU, self.ECC, self.INC, self.RAAN, self.AOP, self.MA, self.ReferenceEpoch, JD, mu) return r, v
def oscelt(self, date, frame="ECLIPJ2000", mu=None): """Concic osculating orbital elements. Returns the orbit from oscelt in the SPICE toolkit. The results are unreliable for eccentricities very close to 1.0, specific angular momentum near zero, and inclinations near 0 or 180 degrees. See the SPICE toolkit for notes. Parameters ---------- date : string, float, astropy Time, datetime Processed via `util.date2time`. frame : string The name of a SPICE reference frame. mu : float, optional `G M` of the primary body, or `None` to use the Sun. Returns ------- orbit : dict Orbital parameters as a dictionary. """ import astropy.units as u from mskpy.ephem import GM_sun from mskpy.util import jd2time et = core.date2et(date) state, lt = spice.spkez(self.naifid, et, frame, "NONE", 10) if mu is None: mu = GM_sun.to('km3 / s2').value o = spice.oscelt(state, et, mu) orbit = {} orbit['q'] = (o[0] * u.km).to(u.au) orbit['e'] = o[1] orbit['i'] = (o[2] * u.rad).to(u.deg) orbit['node'] = (o[3] * u.rad).to(u.deg) orbit['peri'] = (o[4] * u.rad).to(u.deg) orbit['n'] = (o[5] * u.rad).to(u.deg) / u.s orbit['t'] = jd2time(float(core.et2jd(o[6]))) return orbit
def detectCloseApproaches(self, output_file, spacecraft_SPICE_ID, body_SPICE_ID, body_radius, epoch_windows): for window in epoch_windows: # create a close approach object self.close_approaches.append(CloseApproach()) self.close_approaches[-1].target_body_SPICE_ID = body_SPICE_ID self.close_approaches[-1].target_body = spice.bodc2n(body_SPICE_ID) left_epoch = spice.str2et(window[0]) right_epoch = spice.str2et(window[1]) middle_epoch = (right_epoch - left_epoch) / 2.0 + left_epoch left_state, left_light_times = spice.spkez(spacecraft_SPICE_ID, left_epoch, 'J2000', 'NONE', body_SPICE_ID) right_state, right_light_times = spice.spkez( spacecraft_SPICE_ID, right_epoch, 'J2000', 'NONE', body_SPICE_ID) middle_state, middle_light_times = spice.spkez( spacecraft_SPICE_ID, middle_epoch, 'J2000', 'NONE', body_SPICE_ID) left_distance = np.sqrt(left_state[0]**2 + left_state[1]**2 + left_state[2]**2) right_distance = np.sqrt(right_state[0]**2 + right_state[1]**2 + right_state[2]**2) middle_distance = np.sqrt(middle_state[0]**2 + middle_state[1]**2 + middle_state[2]**2) # handle the case where left epoch is post periapse and we must be on an ellipse if middle_distance > left_distance and middle_distance > right_distance: # either left or right must be the closest approach if left_distance > right_distance: # right is closest approach self.close_approaches[-1].spacecraft_state = right_state else: # left is closest approach self.close_approaches[-1].spacecraft_state = left_state self.close_approaches[ -1].spacecraft_altitude = left_distance - body_radius break # middle must be closer than left and right # we need to determine if our middle is before or after periapse # look a little bit into the future to see if we are getting closer to the body or farther away peek_epoch = middle_epoch + 1.0 peek_state, middle_light_times = spice.spkez( spacecraft_SPICE_ID, peek_epoch, 'J2000', 'NONE', body_SPICE_ID) peek_distance = np.sqrt(peek_state[0]**2 + peek_state[1]**2 + peek_state[2]**2) if peek_distance > middle_distance: # we have passed periapse, therefore we need to bracket with left_epoch right_epoch = middle_epoch else: left_epoch = middle_epoch iterations = 0 tol = 0.001 while iterations < 100: middle_epoch = (right_epoch - left_epoch) / 2.0 + left_epoch left_state, left_light_times = spice.spkez( spacecraft_SPICE_ID, left_epoch, 'J2000', 'NONE', body_SPICE_ID) right_state, right_light_times = spice.spkez( spacecraft_SPICE_ID, right_epoch, 'J2000', 'NONE', body_SPICE_ID) middle_state, middle_light_times = spice.spkez( spacecraft_SPICE_ID, middle_epoch, 'J2000', 'NONE', body_SPICE_ID) left_distance = np.sqrt(left_state[0]**2 + left_state[1]**2 + left_state[2]**2) right_distance = np.sqrt(right_state[0]**2 + right_state[1]**2 + right_state[2]**2) middle_distance = np.sqrt(middle_state[0]**2 + middle_state[1]**2 + middle_state[2]**2) if np.abs(left_distance - right_distance) < tol: closest_approach_distance = middle_distance break # determine where the left, middle and right points are located w.r.t. periapse time_step = 0.001 middle_peek_state, middle_light_times = spice.spkez( spacecraft_SPICE_ID, middle_epoch + time_step, 'J2000', 'NONE', body_SPICE_ID) middle_peek_distance = np.sqrt(middle_peek_state[0]**2 + middle_peek_state[1]**2 + middle_peek_state[2]**2) if middle_peek_distance - middle_distance < 0.0: left_epoch = middle_epoch elif middle_peek_distance - middle_distance > 0.0: right_epoch = middle_epoch self.close_approaches[ -1].Julian_date = middle_epoch / 86400.0 + 2451545.0 self.close_approaches[-1].spacecraft_state = middle_state self.close_approaches[ -1].spacecraft_altitude = closest_approach_distance - body_radius
def getPerturberState(body_name, times, frame="ecliptic", origin="heliocenter"): """ Query the JPL ephemeris files loaded in SPICE for the state vectors of desired perturbers. Major bodies and dynamical centers available: 'solar system barycenter', 'sun', 'mercury', 'venus', 'earth', 'mars', 'jupiter', 'saturn', 'uranus', 'neptune' Parameters ---------- body_name : str Name of major body. times : `~astropy.time.core.Time` (N) Times at which to get state vectors. frame : {'equatorial', 'ecliptic'} Return perturber state in the equatorial or ecliptic J2000 frames. origin : {'barycenter', 'heliocenter'} Return perturber state with heliocentric or barycentric origin. Returns ------- states : `~numpy.ndarray` (N, 6) Heliocentric ecliptic J2000 state vector with postion in AU and velocity in AU per day. """ if origin == "barycenter": center = 0 # Solar System Barycenter elif origin == "heliocenter": center = 10 # Heliocenter else: err = ("origin should be one of 'heliocenter' or 'barycenter'") raise ValueError(err) if frame == "ecliptic": frame_spice = "ECLIPJ2000" elif frame == "equatorial": frame_spice = "J2000" else: err = ("frame should be one of {'equatorial', 'ecliptic'}") raise ValueError(err) # Make sure SPICE is ready to roll setupSPICE() # Check that times is an astropy time object _checkTime(times, "times") # Convert MJD epochs in TDB to ET in TDB epochs_tdb = times.tdb epochs_et = np.array( [sp.str2et('JD {:.16f} TDB'.format(i)) for i in epochs_tdb.jd]) # Get position of the body in heliocentric ecliptic J2000 coordinates states = [] for epoch in epochs_et: state, lt = sp.spkez(NAIF_MAPPING[body_name.lower()], epoch, frame_spice, 'NONE', center) states.append(state) states = np.vstack(states) # Convert to AU and AU per day states = states / KM_P_AU states[:, 3:] = states[:, 3:] * S_P_DAY return states
def computeCircleCircleOccultations(self, current_epoch, greg_date_string, JD_date, data_frame): for occlt in data_frame.data['occultations']: # source body position w.r.t. target spacecraft_state_wrt_source, light_times = spice.spkez( spice.bodn2c(occlt.target_name), current_epoch, data_frame.frame, 'NONE', spice.bodn2c(occlt.source_body_name)) source_range = self.np.linalg.norm( spacecraft_state_wrt_source[0:3]) # occultation body positon w.r.t. target spacecraft_state_wrt_occulter, light_times = spice.spkez( spice.bodn2c(occlt.target_name), current_epoch, data_frame.frame, 'NONE', spice.bodn2c(occlt.occulting_body_name)) occulter_range = self.np.linalg.norm( spacecraft_state_wrt_occulter[0:3]) # compute the SPO (source-probe-occulter) angle cosAngle_SPO = self.np.dot(spacecraft_state_wrt_source[0:3], spacecraft_state_wrt_occulter[0:3] ) / source_range / occulter_range SPO_angle = self.np.arccos(cosAngle_SPO) # compute half-angles source_half_angle = self.np.arcsin( data_frame.body_dict[occlt.source_body_name].radius / source_range) occulter_half_angle = self.np.arcsin( data_frame.body_dict[occlt.occulting_body_name].radius / occulter_range) # body areas source_area = self.np.pi * source_half_angle**2 occulter_area = self.np.pi * occulter_half_angle**2 # compute eclipse % eclipse_percentage = 0.0 eclipse_type = "none" # first test if we have an eclipse condition at all if source_half_angle + occulter_half_angle < SPO_angle: eclipse_percentage = 0.0 else: if SPO_angle < occulter_half_angle - source_half_angle: # we are at full eclipse eclipse_percentage = 100.0 eclipse_type = "full" else: if SPO_angle < source_half_angle - occulter_half_angle: # annular eclipse eclipse_percentage = 100.0 * (occulter_area / source_area) eclipse_type = "annular" else: # partial eclipse x1 = 1.0 / 2.0 * (SPO_angle**2 - source_half_angle**2 + occulter_half_angle**2) / SPO_angle a1 = occulter_half_angle**2 * self.np.arccos( x1 / occulter_half_angle) - x1 * self.np.sqrt( occulter_half_angle**2 - x1**2) a2 = source_half_angle**2 * self.np.arccos( (SPO_angle - x1) / source_half_angle) - ( SPO_angle - x1) * self.np.sqrt(source_half_angle**2 - (SPO_angle - x1)**2) eclipse_percentage = (a1 + a2) / source_area eclipse_percentage *= 100.0 eclipse_type = "partial" occlt.data.append( [greg_date_string, JD_date, eclipse_percentage, eclipse_type])
def printReport(self, SPICEbody=399, reportfilename='default_body_distance_report.csv', units='AU'): #we need to load all of the SPICEness import spiceypy import os spiceypy.furnsh(self.myOptions.universe_folder + '/ephemeris_files/' + self.myOptions.SPICE_leap_seconds_kernel) spiceypy.furnsh(self.myOptions.universe_folder + '/ephemeris_files/' + self.myOptions.SPICE_reference_frame_kernel) for dirpath, dirnames, filenames in os.walk( self.myOptions.universe_folder + '/ephemeris_files/'): for file in filenames: sourcefile = os.path.join(dirpath, file) if sourcefile.endswith('.bsp'): spiceypy.furnsh(sourcefile) with open(reportfilename, 'w') as file: #all operations file.write( 'Gregorian date (ET/TDB), Julian date (ET/TDB), Spacecraft distance from ' + spiceypy.bodc2n(SPICEbody) + ' (' + str(SPICEbody) + ') ' + ' (' + units + ')\n') for recordIndex in range(0, len(self.ephemeris_file_data)): myRecord = self.ephemeris_file_data[recordIndex] epoch = myRecord.julian_date seconds_since_J2000 = spiceypy.str2et(str(epoch) + " JD TDB") x_spacecraft = myRecord.spacecraft_position_x y_spacecraft = myRecord.spacecraft_position_y z_spacecraft = myRecord.spacecraft_position_z try: [body_state, light_time] = spiceypy.spkez(SPICEbody, seconds_since_J2000, 'J2000', 'NONE', 10) except: print( 'I don\'t have enough information to tell you the position of ' + str(SPICEbody) + ' with respect to the Sun on ' + epoch + '\n') x_body = body_state[0] y_body = body_state[1] z_body = body_state[2] x_relative = x_spacecraft - x_body y_relative = y_spacecraft - y_body z_relative = z_spacecraft - z_body r_relative = (x_relative**2 + y_relative**2 + z_relative**2)**0.5 if units == 'AU': r_relative /= 149597870.691 elif units != 'km': raise ('I don\'t know what a ' + units + ' is!') file.write(myRecord.gregorian_date + ',' + str(myRecord.julian_date) + ',' + str(r_relative) + '\n') #now we can unload all the SPICE kernels spiceypy.unload(self.myOptions.universe_folder + '/ephemeris_files/' + self.myOptions.SPICE_leap_seconds_kernel) spiceypy.unload(self.myOptions.universe_folder + '/ephemeris_files/' + self.myOptions.SPICE_reference_frame_kernel) for dirpath, dirnames, filenames in os.walk( self.myOptions.universe_folder + '/ephemeris_files/'): for file in filenames: sourcefile = os.path.join(dirpath, file) if sourcefile.endswith('.bsp'): spiceypy.unload(sourcefile)
def getDEstate(target,t,frame='ECLIPJ2000'): return sp.spkez(target,t,frame,'NONE',0)[0]