def transform_lonlat(t, lons, lats, frame_from, frame_to): """Transform longitude/latitude direction from one reference frame to another. Parameters ---------- t : datetime.datetime Evaluation datetimes. lons : np.ndarray Longitudes array. lats : np.ndarray Latitudes array. Returns ------- (np.ndarray, np.ndarray) Transformed longitude/latitude's. Raises ------ ValueError If source and target frame are equal. """ if frame_from == frame_to: raise ValueError("source frame and target frame are equal") lons_rad = 2 * np.pi * lons / 360 lats_rad = 2 * np.pi * lats / 360 vecs = np.array([ [np.cos(lon), np.sin(lon), np.sin(lat)] for (lon, lat) in list(zip(lons_rad, lats_rad)) ]) if isinstance(t, datetime.datetime): for i in range(0, len(vecs)): vecs[i] = spiceypy.mxv(spiceypy.pxform(frame_from, frame_to, spiceypy.datetime2et(t)), vecs[i]) elif isinstance(t, np.ndarray) or isinstance(t, list): for i in range(0, len(t)): vecs[i] = spiceypy.mxv(spiceypy.pxform(frame_from, frame_to, spiceypy.datetime2et(t[i])), vecs[i]) else: raise TypeError("variable t is not a datetime or array/list") res = np.array([ [360 * np.arccos(v[0]) / 2 / np.pi, 360 * np.arcsin(v[2]) / 2 / np.pi] for v in vecs ]) return res
def velocityUlysses(date, RF="HCI"): """ Get Ulysses' eigen-velocity from SPICE and transform it to RTN-coordinates :param date: datetime object :param RF: etspice.ReferenceFrame :return: eigen-velocity in RTN-coordinates """ from spiceypy import spkezr, datetime2et [x, y, z, vx, vy, vz] = spkezr('ULYSSES', datetime2et(date), RF, 'None', 'SUN')[0] # cart in km(/s) r_sph = cart2spher(np.array([x, y, z])) v_sph = cart2spher(np.array( [vx, vy, vz])) #does not really make sense but is needed for the rtn-function if RF in ["HCI", "HCI_B", "HCI_J"]: # solar equatorial coord. sys. pass elif RF in ["ECLIPJ2000", "ECLIPB1950"]: # ecliptic coord. sys. t_epoch = datetime.datetime( 2000, 1, 1, 12) if RF == "ECLIPJ2000" else datetime.datetime( 2000, 1, 1, 12) + datetime.timedelta(days=(2433282.42345905 - 2451545.0)) r_sph = hc_to_hg(r_sph, ang_ascnode=calc_delta(t_epoch)) v_sph = hc_to_hg(v_sph, ang_ascnode=calc_delta(t_epoch)) vR, vT, vN = hg_to_rtn(v_sph, r_sph) return (np.array([vR, vT, vN]))
def find_maven_apsis(segment='periapse'): """ Calculates the ephemeris times at apoapse or periapse for all MAVEN orbits between orbital insertion and now. Requires furnishing of all SPICE kernels. Parameters ---------- segment : str The orbit point at which to calculate the ephemeris time. Choices are 'periapse' and 'apoapse'. Defaults to 'periapse'. Returns ------- orbit_numbers : array Array of MAVEN orbit numbers. et_array : array Array of ephemeris times for chosen orbit segment. """ # set starting and ending times et_start = 464623267 # MAVEN orbital insertion et_end = spice.datetime2et(datetime.utcnow()) # right now # do very complicated SPICE stuff target = 'Mars' abcorr = 'NONE' observer = 'MAVEN' relate = '' refval = 0. if segment == 'periapse': relate = 'LOCMIN' refval = 3396. + 500. elif segment == 'apoapse': relate = 'LOCMAX' refval = 3396. + 6200. adjust = 0. step = 60. # 1 minute steps, since we are only looking within periapse segment for periapsis et = [et_start, et_end] cnfine = spice.utils.support_types.SPICEDOUBLE_CELL(2) spice.wninsd(et[0], et[1], cnfine) ninterval = round((et[1] - et[0]) / step) result = spice.utils.support_types.SPICEDOUBLE_CELL(round(1.1 * (et[1] - et[0]) / 4.5)) spice.gfdist(target, abcorr, observer, relate, refval, adjust, step, ninterval, cnfine, result=result) count = spice.wncard(result) et_array = np.zeros(count) if count == 0: print('Result window is empty.') else: for i in range(count): lr = spice.wnfetd(result, i) left = lr[0] right = lr[1] if left == right: et_array[i] = left # make array of orbit numbers orbit_numbers = np.arange(1, len(et_array) + 1, 1, dtype=int) # return orbit numbers and array of ephemeris times return orbit_numbers, et_array
def caps_all_anodes(tempdatetime): et = spice.datetime2et(tempdatetime) sclkdp = spice.sce2c( -82, et) # converts an et to a continuous encoded sc clock (ticks) caps_els_anode_vecs = [] for anodenumber, x in enumerate(np.arange(70, -90, -20)): # print(anodenumber, x) rotationmatrix_anode = spice.spiceypy.axisar(np.array( [1, 0, 0]), x * spice.rpd()) # Get angles for different anodes # print("rotationmatrix_anode", rotationmatrix_anode) postanode_rotation = spice.vhat( spice.mxv(rotationmatrix_anode, -spice.spiceypy.getfov( -82821, 20)[2])) # Apply rotation for anodes # print("postanode_rotation", postanode_rotation) # print("caps_els_boresight", caps_els_boresight) cassini_caps_mat = spice.ckgp( -82821, sclkdp, 0, 'CASSINI_CAPS_BASE')[0] # Get actuation angle # print("cassini_caps_mat", cassini_caps_mat) cassini_caps_act_vec = spice.mxv( cassini_caps_mat, postanode_rotation) # Rotate with actuator # print("Actuating frame", cassini_caps_act_vec) CAPS_act_2_titan_cmat = spice.ckgp( -82000, sclkdp, 0, 'IAU_TITAN')[0] # Find matrix to transform to IAU_TITAN frame CAPS_act_2_titan_cmat_transpose = spice.xpose( CAPS_act_2_titan_cmat) # Tranpose matrix rotated_vec = spice.mxv(CAPS_act_2_titan_cmat_transpose, cassini_caps_act_vec) # Apply Matrix # print("rotated_vec ", rotated_vec) caps_els_anode_vecs.append(rotated_vec) return caps_els_anode_vecs
def soldir_from_titan(): #Only use for one flyby for counter, flyby in enumerate(flybys): tempdf = windsdf[windsdf['Flyby'] == flyby] i = pd.to_datetime(tempdf['Bulk Time']).iloc[0] et = spice.datetime2et(i) sundir, ltime = spice.spkpos('SUN', et, 'IAU_TITAN', "LT+S", 'TITAN') return sundir
def worker_transform_frame(args): """Worker function for transforming 3D coordinates. Parameters ---------- args: (np.ndarray, np.ndarray, str, str, np.ndarray, np.ndarray, list) Function arguments as tuple. Returns ------- np.ndarray Transformed 3D coordinates. """ (t, data, frame_from, frame_to, frames, frame_indices, kernels) = args if len(heliosat._spice["kernels_loaded"]) == 0: heliosat.spice.spice_init() heliosat.spice.spice_reload(kernels) if frames: for i in range(0, len(t)): data[i] = spiceypy.mxv(frames[int(frame_indices[i])], data[i]) else: for i in range(0, len(t)): data[i] = spiceypy.mxv(spiceypy.pxform(frame_from, frame_to, spiceypy.datetime2et(t[i])), data[i]) return data
def cassini_titan_altlatlon(tempdatetime): et = spice.datetime2et(tempdatetime) state, ltime = spice.spkezr('CASSINI', et, 'IAU_TITAN', 'NONE', 'TITAN') lon, lat, alt = spice.recpgr('TITAN', state[:3], spice.bodvrd('TITAN', 'RADII', 3)[1][0], 2.64e-4) return alt, lat * spice.dpr(), lon * spice.dpr()
def convert_to_inst(self, coords): """ Convert the given coordinates to the instrument frame Parameters ---------- coords : `astropy.coordinate.SkyCoord` The coordinates to transform to the instrument frame frame : `str`, optional The instrument coordinate frame (ILS or OPT) Returns ------- tuple x and y coordinates """ icrs_coords = coords.transform_to('icrs') cart_coords = icrs_coords.represent_as('cartesian') et = spiceypy.datetime2et(coords.obstime.to_datetime()) sc = spiceypy.sce2c(SOLAR_ORBITER_ID, et) cmat, sc = spiceypy.ckgp(SOLAR_ORBITER_STIX_ILS_FRAME_ID, sc, 0, 'J2000') vec = cmat @ cart_coords.xyz.value # Rotate about z so +x towards the Sun # vec = np.array([[-1, 0, 0], [0, -1, 0], [0, 0, 1]]) @ vec distance, latitude, longitude = spiceypy.reclat(vec.reshape(3)) y = (latitude + np.pi) * u.rad x = (np.pi/2 - longitude) * u.rad return x, y
def get_position(self, *, date, frame): """ Get the position of SolarOrbiter at the given date in the given coordinate frame. Parameters ---------- date : `datetime.datetime` Date at which to obtain position frame : `str` Name of the coordinate frame ('IAU_SUN', 'SOLO_HEE') Returns ------- tuple The x, y, and z components of the spacecraft position """ et = spiceypy.datetime2et(date) (x, y, z), lt = spiceypy.spkpos('SOLO', et, frame, 'None', 'SUN') return x*u.km, y*u.km, z*u.km
def datetime_to_scet(self, adatetime): """ Convert datetime to SCET. Parameters ---------- adatetime : `datetime.datetime` or `astropy.time.Time` Time to convert to SCET Returns ------- `str` SCET of datetime encoded as spacecraft clock string """ if isinstance(adatetime, ApTime): adatetime = adatetime.to_datetime() et = spiceypy.datetime2et(adatetime) scet = spiceypy.sce2s(SOLAR_ORBITER_ID, et) return scet
def cassini_ramdirection_SCframe(tempdatetime, target='CASSINI', frame='J2000', observ='titan', corrtn='NONE', output=False): et = spice.datetime2et(tempdatetime) state, ltime = spice.spkezr(target, et, frame, corrtn, observ) ramdir = spice.vhat(state[3:6]) # Gets Attitude sclkdp = spice.sce2c(-82, et) # converts an et to a continuous encoded sc clock (ticks) ckgp_output = spice.ckgp(-82000, sclkdp, 0, frame) cmat = ckgp_output[0] ram_unit = spice.mxv(cmat, ramdir) if output: print('ET = {:20.6f}'.format(et)) print('VX = {:20.6f}'.format(ram_unit[0])) print('VY = {:20.6f}'.format(ram_unit[1])) print('VZ = {:20.6f}'.format(ram_unit[2])) return ram_unit
def get_orientation(self, date, frame): """ Get the orientation or roll, pith and yaw of STIX (ILS or OPT). Parameters ---------- date : `datetime.datetime` Date at which to obtain orientation information frame : `str` Name of the coordinate frame Returns ------- tuple Roll, pitch and yaw of the spacecraft """ et = spiceypy.datetime2et(date) sc = spiceypy.sce2c(SOLAR_ORBITER_ID, et) cmat, sc = spiceypy.ckgp(SOLAR_ORBITER_SRF_FRAME_ID, sc, 0, frame) vec = cmat @ np.eye(3) roll, pitch, yaw = spiceypy.m2eul(vec, 1, 2, 3) roll, pitch, yaw = np.rad2deg([roll, pitch, yaw])*u.deg return roll, pitch, yaw
def trajectory(self, dt: Union[datetime.datetime, Sequence[datetime.datetime]], reference_frame: str = "J2000", observer: str = "SUN", units: str = "AU") -> np.ndarray: logger = logging.getLogger(__name__) dt = sanitize_dt(dt) traj = np.array( spiceypy.spkpos(self.name_naif, spiceypy.datetime2et(dt), reference_frame, "NONE", observer)[0]) if units == "AU": traj *= 6.68459e-9 elif units == "m": traj *= 1e3 elif units == "km": pass else: logger.exception("unit \"%s\" is not supported", units) raise ValueError("unit \"%s\" is not supported", units) return traj
def get_satellite_position(satname, timestamp, kernelpath=None, kernellist=None, refframe="J2000", refobject='SUN', rlonlat=False): """ Returns satellite position from the reference frame of the Sun. Files here: https://sohowww.nascom.nasa.gov/solarsoft/stereo/gen/data/spice/depm/ahead/ and https://stereo-ssc.nascom.nasa.gov/data/moc_sds/ahead/data_products/ephemerides/ Parameters ========== satname : str Name of satellite. Recognised strings: 'stereo' timestamp : datetime.datetime object / list of dt objs Times of positions to return. kernelpath : str (default=None) Optional path to directory containing kernels, else local "kernels" is used. kernellist : str (default=None) Optional list of kernels in directory kernelpath, else local list is used. refframe : str (default='J2000') See SPICE Required Reading Frames. J2000 is standard, HEE/HEEQ are heliocentric. refobject : str (default='Sun') String for reference onject, e.g. 'Sun' or 'Earth' rlonlat : bool (default=False) If True, returns coordinates in (r, lon, lat) format, not (x,y,z). Returns ======= position : array(x,y,z), list of arrays for multiple timestamps Position of satellite in x, y, z with reference frame refframe and Earth as observing body. Returns (r,lon,lat) if rlonlat==True. """ if 'stereo' in satname.lower(): if 'ahead' in satname.lower() or 'a' in satname.lower(): satstr = 'STEREO AHEAD' if 'behind' in satname.lower() or 'b' in satname.lower(): satstr = 'STEREO BEHIND' elif 'dscovr' in satname.lower(): satstr = 'DSCOVR' logger.error("get_satellite_position: DSCOVR kernels not yet implemented!") else: satstr = satname.upper() logger.warning("get_satellite_position: No specific SPICE kernels for {} satellite!".format(satname)) if kernellist == None: kernellist = required_kernels[satstr] if kernelpath == None: kernelpath = os.path.join('data/kernels') logger.info("get_satellite_position: Reading SPICE kernel files from {}".format(kernelpath)) for k in kernellist: spiceypy.furnsh(os.path.join(kernelpath, k)) time = spiceypy.datetime2et(timestamp) position, lighttimes = spiceypy.spkpos(satstr, time, refframe, 'NONE', refobject) if rlonlat: if len(position) > 3: return np.asarray([spiceypy.reclat(x) for x in position]) return spiceypy.reclat(position) else: return position
def transform_frame(t, data, frame_from, frame_to, frame_cadence=None): """Transform 3D coordinates from one reference frame to another. Parameters ---------- t : list[datetime.datetime] Evaluation datetimes. data : np.ndarray 3D vector array. frame_from : str Source refernce frame. frame_to : str Target reference frame. frame_cadence: float, optional Evaluate frame transformation matrix every "frame_cadence" seconds instead of at very time point (significant speed up), by default None. Returns ------- np.ndarray Transformed vector array in target reference frame. Raises ------ ValueError If data array has the wrong dimensions (must be 2d or 3d) or source and target frame are equal. """ if frame_from == frame_to: raise ValueError("source frame and target frame are equal") # convert timestamps to python datetimes if required if not isinstance(t[0], datetime.datetime): t = [datetime.datetime.fromtimestamp(_t) for _t in t] if frame_cadence: frames = int((t[-1] - t[0]).total_seconds() // frame_cadence) frame_indices = [np.floor(_) for _ in np.linspace(0, frames, len(t), endpoint=False)] time_indices = np.linspace(0, len(t), frames, endpoint=False) frames = [spiceypy.pxform(frame_from, frame_to, spiceypy.datetime2et(t[int(i)])) for i in time_indices] else: frames = None frame_indices = None if data.ndim == 2: if frame_cadence: for i in range(0, len(t)): data[i] = spiceypy.mxv(frames[int(frame_indices[i])], data[i]) else: for i in range(0, len(t)): data[i] = spiceypy.mxv(spiceypy.pxform(frame_from, frame_to, spiceypy.datetime2et(t[i])), data[i]) return data elif data.ndim == 3: max_workers = min(multiprocessing.cpu_count() * 2, data.shape[1]) kernels = heliosat._spice["kernels_loaded"] with ProcessPoolExecutor(max_workers=max_workers) as executor: args = [(t, data[:, i], frame_from, frame_to, frames, frame_indices, kernels) for i in range(data.shape[1])] futures = executor.map(worker_transform_frame, args) result = np.array([_ for _ in futures]) return np.swapaxes(result, 0, 1) else: raise ValueError("data array can only be 2 or 3 dimensional")
#%% # Set an empty array that will store the distances between the Sun # and ATLAS atlas_vecs = [] # Set an empty array that will store the distances between the Sun # and the Solar Orbiter solar_orb_vecs = [] # Iterate through the time array (comet ATLAS) for atlas_time_step in TIME_ARRAY: # Compute the ET atlas_et = spiceypy.datetime2et(atlas_time_step) # Compute the ET corresponding state vector of the comet ATLAS atlas_state_vec = spiceypy.conics(ATLAS_SPICE_ORB_EL, atlas_et) # Store the position vector atlas_vecs.append(atlas_state_vec[:3]) # Iterate through the time array (Solar Orbiter) for so_time_step in TIME_ARRAY: # Compute the ET so_et = spiceypy.datetime2et(so_time_step) # Compute the state vector of the Solar Orbiter (NAIF ID: -144) solar_orb_state_vec, _ = spiceypy.spkgeo(targ=-144, et=so_et, \
def caps_crosstrack_spice(tempdatetime, windspeed): et = spice.datetime2et(tempdatetime) sclkdp = spice.sce2c( -82, et) # converts an et to a continuous encoded sc clock (ticks) state, ltime = spice.spkezr("CASSINI", et, "IAU_TITAN", "NONE", "titan") ramdir = spice.vhat(state[3:6]) # print("ramdir",ramdir) # Gets Attitude sclkdp = spice.sce2c( -82, et) # converts an et to a continuous encoded sc clock (ticks) ckgp_output = spice.ckgp(-82000, sclkdp, 0, "IAU_TITAN") cmat = ckgp_output[0] print("cmat", cmat) ram_unit = spice.mxv(cmat, ramdir) # Ram Unit in SC coords # print("ram_unit", ram_unit) anglediff = spice.vsepg( ram_unit[:2], np.array([0, 1, 0]), 2) # Find azimuthal angle between normal boresight and ram direction # print("anglediff", anglediff * spice.dpr()) cassini_ram_mat = spice.rotate(-anglediff, 3) # print("cassini_ram_mat", cassini_ram_mat) # Rotates rotational axis with actuation # cassini_caps_mat = spice.ckgp(-82821, sclkdp, 0, 'CASSINI_CAPS_BASE')[0] # Rotation matrix of actuation # print("cassini_caps_mat", cassini_caps_mat) anode_rotational_axis = spice.mxv(cassini_ram_mat, np.array([1, 0, 0])) # Rotate with actuator print("Rotational Axis", anode_rotational_axis) rotationmatrix_1 = spice.spiceypy.axisar(anode_rotational_axis, -70 * spice.rpd()) rotationmatrix_2 = spice.spiceypy.axisar(anode_rotational_axis, 70 * spice.rpd()) ram_unit_rotated1 = spice.mxv(rotationmatrix_1, ram_unit) ram_unit_rotated2 = spice.mxv(rotationmatrix_2, ram_unit) scframe_spiceplane = spice.psv2pl([0, 0, 0], ram_unit_rotated1, ram_unit_rotated2) print("ram_unit", ram_unit, ram_unit_rotated1, ram_unit_rotated2) print("SC frame spice normal", spice.psv2pl([0, 0, 0], ram_unit_rotated1, ram_unit_rotated2)) cmat_t = spice.xpose(cmat) ram_unit_rotated1_titan = spice.mxv( cmat_t, ram_unit_rotated1) # Transform back to IAU Titan Frame ram_unit_rotated2_titan = spice.mxv( cmat_t, ram_unit_rotated2) # Transform back to IAU Titan Frame spiceplanenormal = spice.mxv(cmat_t, spice.pl2nvp(scframe_spiceplane)[0]) # Old method finding normal in titan frame # spiceplane = spice.psv2pl(state[:3], ram_unit_rotated1_titan, ram_unit_rotated2_titan) # spiceplanenormal = spice.pl2nvp(spiceplane)[0] print("SPICE NORMAL", spiceplanenormal) # print("Spice normal, sc frame", scframe_spicenormal_titan) if windspeed > 0: spiceplanenormal = -1 * spiceplanenormal print("spice plane fipped", windspeed, spiceplanenormal) print("vsep titan frame", spice.vsep(ramdir, spiceplanenormal) * spice.dpr()) return spiceplanenormal, ram_unit_rotated1_titan, ram_unit_rotated2_titan
def find_maven_apsis_et(self, end_time: datetime = None, apsis: str = 'apoapse') \ -> tuple[np.ndarray, np.ndarray]: """Calculate the ephemeris times at either orbital apses between a start and end time. To do this, SPICE checks the Mars-MAVEN distance in steps of 1 minute, and returns the local minima (for periapsis) or local maxima (for apoapsis). Parameters ---------- end_time Ending datetime to get apsis ephemeris times for. Default is :code:`None`, which will get times up until today. apsis The apsis to get the ephemeris times for. Can be either 'apoapse' or 'periapse'. Returns ------- orbits Relative MAVEN orbit numbers between the start and end dates. et Ephemeris times at the given apsis for all the orbits in the time range. """ # TODO: it'd be nice to be able to specify a start time. This is easy to # implement here, but I'm not sure how I'd compute orbit numbers. et_start = 464623267 et_end = spice.datetime2et(datetime.utcnow()) if end_time is None \ else spice.datetime2et(end_time) relate, refval = self.__set_apsis_flags(apsis) adjust = 0. step = 60. cnfine = spice.utils.support_types.SPICEDOUBLE_CELL(2) spice.wninsd(et_start, et_end, cnfine) ninterval = round((et_end - et_start) / step) result = spice.utils.support_types.SPICEDOUBLE_CELL( round(1.1 * (et_end - et_start) / 4.5)) spice.gfdist(self.__target, self.__abcorr, self.__observer, relate, refval, adjust, step, ninterval, cnfine, result=result) count = spice.wncard(result) et_array = np.zeros(count) if count == 0: print('Result window is empty.') else: for i in range(count): lr = spice.wnfetd(result, i) left = lr[0] right = lr[1] if left == right: et_array[i] = left # make array of orbit numbers orbit_numbers = np.arange(1, len(et_array) + 1, 1, dtype=int) # return orbit numbers and array of ephemeris times return orbit_numbers, et_array
def get_orientation(dt, frame1='SOLO_SRF', frame2='SOLO_EQUAT_NORM'): """ Get the orientation or roll, pith and yaw of STIX (ILS or OPT). Taken from https://github.com/i4Ds/STIXCore/blob/9a765a33f2e924ead669b9b99afc1e41a4d2d8e8/stixcore /ephemeris/tests/test_position.py#L28-L40 Parameters ---------- date : `datetime.datetime` Date at which to obtain orientation information frame : `str` Name of the coordinate frame Returns ------- tuple Roll, pitch and yaw of the spacecraft SOLO mission specific generic frames: SOLO_SUN_RTN Sun Solar Orbiter Radial-Tangential-Normal SOLO_SOLAR_MHP S/C-centred mirror helioprojective SOLO_IAU_SUN_2009 Sun Body-Fixed based on IAU 2009 report SOLO_IAU_SUN_2003 Sun Body-Fixed based on IAU 2003 report SOLO_GAE Geocentric Aries Ecliptic at J2000 (GAE) SOLO_GSE Geocentric Solar Ecliptic at J2000 (GSE) SOLO_HEE Heliocentric Earth Ecliptic at J2000 (HEE) SOLO_VSO Venus-centric Solar Orbital (VSO) Heliospheric Coordinate Frames developed for the NASA STEREO mission: SOLO_ECLIPDATE Mean Ecliptic of Date Frame SOLO_HCI Heliocentric Inertial Frame SOLO_HEE_NASA Heliocentric Earth Ecliptic Frame SOLO_HEEQ Heliocentric Earth Equatorial Frame SOLO_GEORTN Geocentric Radial Tangential Normal Frame Heliocentric Generic Frames(*): SUN_ARIES_ECL Heliocentric Aries Ecliptic (HAE) SUN_EARTH_CEQU Heliocentric Earth Equatorial (HEEQ) SUN_EARTH_ECL Heliocentric Earth Ecliptic (HEE) SUN_INERTIAL Heliocentric Inertial (HCI) Geocentric Generic Frames: EARTH_SUN_ECL (*) Geocentric Solar Ecliptic (GSE) EARTH_MECL_MEQX (*) Earth Mean Ecliptic and Equinox of date frame (Auxiliary frame for EARTH_SUN_ECL) EARTH_MECL_MEQX_J2000 Earth Mean Ecliptic and Equinox at J2000 frame (Auxiliary frame for SOLO_GSE and SOLO_HEE) """ et = sp.datetime2et(dt) sc = sp.sce2c(SOLAR_ORBITER_ID, et) #convert to clock ticks #tol=sp.sctiks(int(sc), "1:000") tol= 1.0 #cmat, sc= sp.ckgp(SOLAR_ORBITER_SRF_FRAME_ID, sc, tol, 'SOLO_ECLIP_NORM') #cmat, sc= sp.ckgp(SOLAR_ORBITER_SRF_FRAME_ID, sc, tol, 'SOLO_EQUAT_NORM') frame_id=SOLAR_ORBITER_SRF_FRAME_ID if frame1=='SOLO_SRF' else SOLAR_ORBITER_STIX_OPT_FRAME_D cmat, sc= sp.ckgp(frame_id, sc, tol, frame2) #get c-matrix orientiation information roll, pitch, yaw = sp.m2eul(cmat, 1, 2, 3) #matrix to Euler angles # Following lines taken from from get_sunspice_roll.pro #https://www.heliodocs.com/xdoc/xdoc_list.php?dir=$SSW/packages/sunspice/idl #if abs(roll) > 2 *math.pi: # roll=roll-math.copysign(2*math.pi,roll) #if abs(pitch) > 0.5* math.pi: # #taken from idl code get_sunspice_roll.pro # pitch = math.copysign(2*math.pi, pitch) - pitch # yaw = yaw - math.copysign(2*math.pi, yaw) # roll = roll - math.copysign(2*math.pi, roll) roll, pitch, yaw = np.rad2deg([roll, pitch, yaw])#convert to arcmin #take from Frederic idl code #if yaw <0: # yaw+=360 #yaw = yaw-180 pitch = -pitch roll = -roll return (roll, pitch, yaw)
def get_flare_spice(sun_x,sun_y,flare_utc, observer='earth'): #sun_x, sun_y in unites of arcsec date_flare = stix_datetime.utc2datetime(flare_utc) et_stix = sp.datetime2et(date_flare) flare_coord= [sun_x, sun_y] # Obtain the coordinates of Solar Orbiter earth_hee_spice, ltime_earth_sun = sp.spkpos('EARTH', et_stix, 'SOLO_HEE_NASA', # Reference frame of the output position vector of the object 'NONE', 'SUN') earth_hee_spice = earth_hee_spice * u.km # Convert the coordinates to HEE earth_hee = HeliocentricEarthEcliptic(earth_hee_spice, obstime=Time(date_flare).isot, representation_type='cartesian') solo_hee_spice,ltime_sun_solo = sp.spkpos('SOLO', et_stix, 'SOLO_HEE_NASA', 'NONE', 'SUN') solo_hee_spice = solo_hee_spice * u.km # Convert the coordinates to HEE solo_hee = HeliocentricEarthEcliptic(solo_hee_spice, obstime=Time(date_flare).isot, representation_type='cartesian') if observer=='earth': obs_coord=earth_hee#.transform_to(HeliographicStonyhurst(obstime=date_flare)) else: obs_coord=solo_hee#.transform_to(HeliographicStonyhurst(obstime=date_flare) ) flare_skycoord= SkyCoord(flare_coord[0]*u.arcsec, flare_coord[1]*u.arcsec, obstime=date_flare, observer=obs_coord, frame='helioprojective') flare_hee = flare_skycoord.transform_to(HeliocentricEarthEcliptic(obstime=date_flare)) v_flare_earth=earth_hee.cartesian-flare_hee.cartesian flare_hee_cart=flare_hee.cartesian flare_earth_r=np.sqrt(v_flare_earth.x**2+v_flare_earth.y**2+v_flare_earth.z**2) v_flare_solo=-flare_hee.cartesian+solo_hee.cartesian flare_solo_r=np.sqrt(v_flare_solo.x**2+v_flare_solo.y**2+v_flare_solo.z**2) ves=solo_hee.cartesian-earth_hee.cartesian v_earth_solo=np.sqrt(ves.x**2+ves.y**2+ves.z**2) flare_earth_t=flare_earth_r.to(u.m) / const.c flare_solo_t=flare_solo_r.to(u.m) / const.c owlt=v_earth_solo.to(u.m) / const.c flare_theta_stix=vsep(flare_hee_cart, v_flare_solo) flare_theta_earth=vsep(flare_hee_cart, v_flare_earth) return {'flare_earth_lt':flare_earth_t.value, 'flare_solo_lt':flare_solo_t.value, 'owlt':owlt.value, 'dt': (flare_earth_t-flare_solo_t).value, 'flare_solo_r':flare_solo_r.to(u.au).value, 'dt_solar_center':ltime_earth_sun-ltime_sun_solo, 'flare_utc':flare_utc, 'theta_flare_norm_earth_deg':flare_theta_earth, 'theta_flare_norm_solo_deg':flare_theta_stix, 'earth_sun_ltime': ltime_earth_sun, 'sun_solo_ltime': ltime_sun_solo, 'observer':observer }
def get_earth_spice_HEE(datetimes): ets=[sp.datetime2et(t) for t in datetimes] pos_vel, light_times= sp.spkezr('Earth', ets, 'SOLO_HEE_NASA', 'LT+S', 'Sun') positions = np.array(pos_vel)[:, :3] * u.km return {'light_times':np.array(light_times),'positions':positions.to(u.au)}
def get_satellite_position(satname, timestamp, kernelpath=None, kernellist=None, refframe="J2000", refobject='SUN', rlonlat=False): """ Returns satellite position from the reference frame of the Sun. Files here: https://sohowww.nascom.nasa.gov/solarsoft/stereo/gen/data/spice/depm/ahead/ and https://stereo-ssc.nascom.nasa.gov/data/moc_sds/ahead/data_products/ephemerides/ Parameters ========== satname : str Name of satellite. Recognised strings: 'stereo' timestamp : datetime.datetime object / list of dt objs Times of positions to return. kernelpath : str (default=None) Optional path to directory containing kernels, else local "kernels" is used. kernellist : str (default=None) Optional list of kernels in directory kernelpath, else local list is used. refframe : str (default='J2000') See SPICE Required Reading Frames. J2000 is standard, HEE/HEEQ are heliocentric. refobject : str (default='Sun') String for reference onject, e.g. 'Sun' or 'Earth' rlonlat : bool (default=False) If True, returns coordinates in (r, lon, lat) format, not (x,y,z). Returns ======= position : array(x,y,z), list of arrays for multiple timestamps Position of satellite in x, y, z with reference frame refframe and Earth as observing body. Returns (r,lon,lat) if rlonlat==True. """ if 'stereo' in satname.lower(): if 'ahead' in satname.lower() or 'a' in satname.lower(): satstr = 'STEREO AHEAD' if 'behind' in satname.lower() or 'b' in satname.lower(): satstr = 'STEREO BEHIND' elif 'dscovr' in satname.lower(): satstr = 'DSCOVR' logger.error( "get_satellite_position: DSCOVR kernels not yet implemented!") else: satstr = satname.upper() logger.warning( "get_satellite_position: No specific SPICE kernels for {} satellite!" .format(satname)) if kernellist == None: kernellist = required_kernels[satstr] if kernelpath == None: kernelpath = os.path.join('data/kernels') logger.info( "get_satellite_position: Reading SPICE kernel files from {}".format( kernelpath)) for k in kernellist: spiceypy.furnsh(os.path.join(kernelpath, k)) time = spiceypy.datetime2et(timestamp) position, lighttimes = spiceypy.spkpos(satstr, time, refframe, 'NONE', refobject) if rlonlat: if len(position) > 3: return np.asarray([spiceypy.reclat(x) for x in position]) return spiceypy.reclat(position) else: return position