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 Orientation(self, frame='', target_frame='', current=False, format='msop quaternions'): if self.target and not target_frame: target_frame = self.target.frame if not self.target and not target_frame: target_frame = 'J2000' if not frame: frame = self.frame if not current: current = self.time.current else: #TODO: need to add a time conversion here current = current rot_mat = spiceypy.pxform(target_frame, frame, current) if format == 'spice quaternions': orientation = spiceypy.m2q(rot_mat) if format == 'msop quaternions': quaternions = spiceypy.m2q(rot_mat) orientation = [-quaternions[1], -quaternions[2], -quaternions[3], quaternions[0]] elif format == 'euler angles': orientation = (spiceypy.m2eul(rot_mat, 3, 2, 1)) elif format == 'rotation matrix': orientation = rot_mat return orientation
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)
kwargs = {'from_ref': from_ref, 'to_ref': to_ref} tais = utc2tai(utc, utc_end, deltat) return distribute_work(xform_spice, xfunc, tais, **kwargs) def utc2scs(utc, utc_end, deltat, sc): tais = utc2tai(utc, utc_end, deltat) return utc2scs_spice(tais, sc) def scs2utc(scs, sc, deltat): return scs2utc_spice(scs, sc, deltat) _POSITION_KIND = { None : lambda x: x, 'rectangular': lambda x: x, 'latitudinal': sp.reclat, 'radec' : sp.recrad, 'spherical' : sp.recsph, 'cylindrical': sp.reccyl, 'geodetic' : lambda x: sp.recgeo(x, 6378.14, 0.0033536422844278) } _TRANSFORM_KIND = { None : lambda x: x, 'matrix' : lambda x: x, 'angle' : lambda x: sp.m2eul(x, 3, 2, 1), 'quaternion' : sp.m2q }
def isd_from_json(data, meta): instrument_name = { 'IMAGING SCIENCE SUBSYSTEM NARROW ANGLE': 'CASSINI_ISS_NAC', 'IMAGING SCIENCE SUBSYSTEM WIDE ANGLE': 'CASSINI_ISS_WAC', 'IMAGING SCIENCE SUBSYSTEM - NARROW ANGLE': 'CASSINI_ISS_NAC', 'MDIS-NAC': 'MSGR_MDIS_NAC', 'MERCURY DUAL IMAGING SYSTEM NARROW ANGLE CAMERA': 'MSGR_MDIS_NAC', 'MERCURY DUAL IMAGING SYSTEM WIDE ANGLE CAMERA': 'MSGR_MDIS_WAC' } spacecraft_names = {'CASSINI ORBITER': 'CASSINI', 'MESSENGER': 'MESSENGER'} # This is the return dict isd = {} # Meta kernels are keyed by body, spacecraft, and year - grab from the data spacecraft_name = spacecraft_names[data['spacecraft_id']] target_name = data['target_name'] time = parser.parse(data['capture_date']) for k in meta: if k.year.year == time.year: obs_kernels = k.path # Load the meta kernel spice.furnsh(obs_kernels) path, tpe, handle, found = spice.kdata(0, 'TEXT') if not found: directory = os.path.dirname(path) directory = os.path.abspath(os.path.join(directory, '../iak')) additional_ik = glob.glob(directory + '/*.ti') spice.furnsh(additional_ik) # Spice likes ids over names, so grab the ids from the names instrument_name = instrument_name[data['instrument']] spacecraft_id = spice.bods2c(spacecraft_name) ikid = spice.bods2c(instrument_name) # Load the instrument and target metadata into the ISD isd['instrument_id'] = instrument_name isd['target_name'] = target_name isd['spacecraft_name'] = spacecraft_name # Prepend IAU to all instances of the body name reference_frame = 'IAU_{}'.format(target_name) # Load information from the IK kernel isd['focal_length'] = spice.gdpool('INS{}_FOCAL_LENGTH'.format(ikid), 0, 1) isd['focal_length_epsilon'] = spice.gdpool( 'INS{}_FL_UNCERTAINTY'.format(ikid), 0, 1) isd['nlines'] = spice.gipool('INS{}_PIXEL_LINES'.format(ikid), 0, 1) isd['nsamples'] = spice.gipool('INS{}_PIXEL_SAMPLES'.format(ikid), 0, 1) isd['original_half_lines'] = isd['nlines'] / 2.0 isd['original_half_samples'] = isd['nsamples'] / 2.0 isd['pixel_pitch'] = spice.gdpool('INS{}_PIXEL_PITCH'.format(ikid), 0, 1) isd['ccd_center'] = spice.gdpool('INS{}_CCD_CENTER'.format(ikid), 0, 2) isd['ifov'] = spice.gdpool('INS{}_IFOV'.format(ikid), 0, 1) isd['boresight'] = spice.gdpool('INS{}_BORESIGHT'.format(ikid), 0, 3) isd['transx'] = spice.gdpool('INS{}_TRANSX'.format(ikid), 0, 3) isd['transy'] = spice.gdpool('INS{}_TRANSY'.format(ikid), 0, 3) isd['itrans_sample'] = spice.gdpool('INS{}_ITRANSS'.format(ikid), 0, 3) isd['itrans_line'] = spice.gdpool('INS{}_ITRANSL'.format(ikid), 0, 3) try: isd['odt_x'] = spice.gdpool('INS-{}_OD_T_X'.format(ikid), 0, 10) except: isd['odt_x'] = np.zeros(10) isd['odt_x'][1] = 1 try: isd['odt_y'] = spice.gdpool('INS-{}_OD_T_Y'.format(ikid), 0, 10) except: isd['odt_y'] = np.zeros(10) isd['odt_y'][2] = 1 try: isd['starting_detector_sample'] = spice.gdpool( 'INS{}_FPUBIN_START_SAMPLE'.format(ikid), 0, 1) except: isd['starting_detector_sample'] = 0 try: isd['starting_detector_line'] = spice.gdpool( 'INS{}_FPUBIN_START_LINE'.format(ikid), 0, 1) except: isd['starting_detector_line'] = 0 # Get temperature from SPICE and adjust focal length if 'focal_plane_temperature' in data.keys(): try: # TODO: Remove once WAC temperature dependent is working temp_coeffs = spice.gdpool('INS-{}_FL_TEMP_COEFFS'.format(ikid), 0, 6) temp = data['focal_plane_temperature'] isd['focal_length'] = distort_focal_length(temp_coeffs, temp) except: isd['focal_length'] = spice.gdpool( 'INS-{}_FOCAL_LENGTH'.format(ikid), 0, 1) else: isd['focal_length'] = spice.gdpool('INS-{}_FOCAL_LENGTH'.format(ikid), 0, 1) # Get the radii from SPICE rad = spice.bodvrd(isd['target_name'], 'RADII', 3) radii = rad[1] isd['semi_major_axis'] = rad[1][0] isd['semi_minor_axis'] = rad[1][1] # Now time sclock = data['spacecraft_clock_count'] exposure_duration = data['exposure_duration'] exposure_duration = exposure_duration * 0.001 # Scale to seconds # Get the instrument id, and, since this is a framer, set the time to the middle of the exposure et = spice.scs2e(spacecraft_id, sclock) et += (exposure_duration / 2.0) isd['ephemeris_time'] = et # Get the Sensor Position loc, _ = spice.spkpos(isd['target_name'], et, reference_frame, 'LT+S', spacecraft_name) loc *= -1000 isd['x_sensor_origin'] = loc[0] isd['y_sensor_origin'] = loc[1] isd['z_sensor_origin'] = loc[2] # Get the rotation angles from MDIS NAC frame to Mercury body-fixed frame camera2bodyfixed = spice.pxform(instrument_name, reference_frame, et) opk = spice.m2eul(camera2bodyfixed, 3, 2, 1) isd['omega'] = opk[2] isd['phi'] = opk[1] isd['kappa'] = opk[0] # Get the sun position sun_state, lt = spice.spkezr("SUN", et, reference_frame, data['lighttime_correction'], target_name) # Convert to meters isd['x_sun_position'] = sun_state[0] * 1000 isd['y_sun_position'] = sun_state[1] * 1000 isd['z_sun_position'] = sun_state[2] * 1000 # Get the velocity v_state, lt = spice.spkezr(spacecraft_name, et, reference_frame, data['lighttime_correction'], target_name) isd['x_sensor_velocity'] = v_state[3] * 1000 isd['y_sensor_velocity'] = v_state[4] * 1000 isd['z_sensor_velocity'] = v_state[5] * 1000 # Misc. insertion # A lookup here would be smart - similar to the meta kernals, what is the newest model, etc. if 'model_name' not in data.keys(): isd['model_name'] = 'ISIS_MDISNAC_USGSAstro_1_Linux64_csm30.so' isd['min_elevation'] = data['min_elevation'] isd['max_elevation'] = data['max_elevation'] spice.unload(obs_kernels) # Also unload iak return isd
def maven_orbit_image(time, camera_pos=[1, 0, 0], camera_up=[0, 0, 1], extent=3, parallel_projection=True, view_from_orbit_normal=False, view_from_periapsis=False, show_maven=False, show_orbit=True, label_poles=None, show=True, transparent_background=False, background_color=(0, 0, 0)): """Creates an image of Mars and the MAVEN orbit at a specified time. Parameters ---------- time : str Time to diplay, in a string format interpretable by spiceypy.str2et. camera_pos : length 3 iterable Position of camera in MSO coordinates. camera_up : length 3 iterable Vector defining the image vertical. extent : float Half-width of image in Mars radii. parallel_projection : bool Whether to display an isomorphic image from the camera position. If False, goofy things happen. Defaults to True. view_from_orbit_normal : bool Override camera_pos with a camera position along MAVEN's orbit normal. Defaults to False. view_from_periapsis : bool Override camera_pos with a camera position directly above MAVEN's periapsis. Defaults to False. show_maven : bool Whether to draw a circle showing the position of MAVEN at the specified time. Defaults to False. show_orbit : bool Whether to draw the MAVEN orbit. Defaults to True. label_poles : bool Whether to draw an 'N' and 'S' above the visible poles of Mars. show : bool Whether to show the image when called, or supress display. Defaults to True. transparent_background : bool If True, the image background is transparent, otherwise it is set to background_color. Defaults to False. background_color : RGB1 tuple Background color to use if transparent_background=False. Specified as an RGB tuple with values between 0 and 1. Returns ------- rgb_array : 1000x1000x3 numpy array of image RGB values Image RGB values. return_coords : dict Description of the image coordinate system useful for plotting on top of output image. Notes ----- Call maven_iuvs.load_iuvs_spice() before calling this function to ensure kernels are loaded. """ myet = spice.str2et(time) # disable mlab display (this is done by matplotlib later) mlab.options.offscreen = True # create a figure window (and scene) mlab_pix = 1000 mfig = mlab.figure(size=(mlab_pix, mlab_pix), bgcolor=background_color) # disable rendering as objects are added mfig.scene.disable_render = True # # Set up the planet surface # # load and map the Mars surface texture image_file = os.path.join(anc_dir, 'marssurface_2.jpg') img = tvtk.JPEGReader() img.file_name = image_file texture = tvtk.Texture(input_connection=img.output_port, interpolate=1) # attach the texture to a sphere mars_radius = 3395. sphere_radius = 1 # radius of planet is 1 rM sphere_resolution = 180 # 180 points on the sphere sphere = tvtk.TexturedSphereSource(radius=sphere_radius, theta_resolution=sphere_resolution, phi_resolution=sphere_resolution) sphere_mapper = tvtk.PolyDataMapper(input_connection=sphere.output_port) mars = tvtk.Actor(mapper=sphere_mapper, texture=texture) # adjust the reflection properties for a pretty image mars.property.ambient = 0.2 # so the nightside is slightly visible mars.property.specular = 0.15 # make it shinier near dayside # now apply the rotation matrix to the planet # tvtk only thinks about rotations with Euler angles, so we need # to use a SPICE routine to get these from the rotation matrix # to get from the surface to MSO coordinates we'd normally do # this: rmat = spice.pxform('IAU_MARS', 'MAVEN_MSO', myet) # but we need to use transpose because spice.m2eul assumes the matrix # defines a coordinate system rotation, the inverse of the matrix # to rotate vectors trmat = spice.pxform('MAVEN_MSO', 'IAU_MARS', myet) # now we can get the Euler angles rangles = np.rad2deg(spice.m2eul(trmat, 2, 1, 3)) # ^^^^^^^^ # 2,1,3 because vtk performs # rotations in the order # z,x,y and SPICE wants these # in REVERSE order mars.orientation = rangles[[1, 0, 2]] # ^^^^^^^ # orientation must be specified as x,y,z # rotations in that order even though they # are applied in the order above # OK, that was hard, but now we're good! mfig.scene.add_actor(mars) # # make a lat/lon grid # line_x = [] line_y = [] line_z = [] line_o = [] line_t = np.linspace(0, 2*np.pi, 100) line_r = 1.0 longrid = np.arange(0, 360, 30) for lon in longrid: line_x.append(line_r*np.cos(np.deg2rad(lon))*np.cos(line_t)) line_x.append([0]) line_y.append(line_r*np.sin(np.deg2rad(lon))*np.cos(line_t)) line_y.append([0]) line_z.append(line_r*np.sin(line_t)) line_z.append([0]) line_o.append(np.ones_like(line_t)) line_o.append([0]) latgrid = np.arange(-90, 90, 30)[1:] for lat in latgrid: line_x.append(line_r*np.cos(np.deg2rad(lat))*np.cos(line_t)) line_x.append([0]) line_y.append(line_r*np.cos(np.deg2rad(lat))*np.sin(line_t)) line_y.append([0]) line_z.append(line_r*np.sin(np.deg2rad(lat))*np.ones_like(line_t)) line_z.append([0]) line_o.append(np.ones_like(line_t)) line_o.append([0]) line_x = np.concatenate(line_x) line_y = np.concatenate(line_y) line_z = np.concatenate(line_z) line_o = np.concatenate(line_o) linearray = [np.matmul(rmat, [x, y, z]) for x, y, z in zip(line_x, line_y, line_z)] (line_x, line_y, line_z) = np.transpose(np.array(linearray)) grid_linewidth = 0.25*mlab_pix/1000 mlab.plot3d(line_x, line_y, line_z, line_o, transparent=True, color=(0, 0, 0), tube_radius=None, line_width=grid_linewidth) # # compute the spacecraft orbit # # for the given time, we determine the orbit period maven_state = spice.spkezr('MAVEN', myet, 'MAVEN_MME_2000', 'NONE', 'MARS')[0] marsmu = spice.bodvrd('MARS', 'GM', 1)[1][0] maven_elements = spice.oscltx(maven_state, myet, marsmu) orbit_period = 1.001*maven_elements[-1] # make an etlist corresponding to the half-orbit ahead and behind orbit_subdivisions = 2000 etlist = (myet - orbit_period/2 + orbit_period*np.linspace(0, 1, num=orbit_subdivisions)) # get the position of the orbit in MSO statelist = spice.spkezr('MAVEN', etlist, 'MAVEN_MSO', 'NONE', 'MARS')[0] statelist = np.append(statelist, [statelist[0]], axis=0) # close the orbit poslist = np.transpose(statelist)[:3]/mars_radius # scale to Mars radius # plot the orbit orbitcolor = np.array([222, 45, 38])/255 # a nice red orbitcolor = tuple(orbitcolor) maven_x, maven_y, maven_z = poslist if show_orbit: mlab.plot3d(maven_x, maven_y, maven_z, color=orbitcolor, tube_radius=None, line_width=3*mlab_pix/1000) if not parallel_projection: # add a dot indicating the location of the Sun # this only makes sense with a perspective transform... with # orthographic coordinates we're always too far away # TODO: non parallel projection results in goofy images sun_distance = 10 sun_sphere = tvtk.SphereSource(center=(sun_distance, 0, 0), radius=1*np.pi/180*sun_distance, theta_resolution=sphere_resolution, phi_resolution=sphere_resolution) sun_sphere_mapper = tvtk.PolyDataMapper(input_connection=sun_sphere.output_port) sun_sphere = tvtk.Actor(mapper=sun_sphere_mapper) sun_sphere.property.ambient = 1.0 sun_sphere.property.lighting = False # mfig.scene.add_actor(sun_sphere) # put a line along the x-axis towards the sun # sunline_x=np.arange(0, 5000, 1) # mlab.plot3d(sunline_x, 0*sunline_x, 0*sunline_x, # color=(1.0,1.0,1.0), # tube_radius=None,line_width=6) # # Define camera coordinates # if view_from_periapsis: # to do this we need to get the position of apoapsis and the # orbit normal rlist = [np.linalg.norm(p) for p in np.transpose(poslist)] apoidx = np.argmax(rlist) apostate = spice.spkezr('MAVEN', etlist[apoidx], 'MAVEN_MSO', 'NONE', 'MARS')[0] camera_pos = -1.0 * apostate[:3] camera_pos = 5 * (camera_pos/np.linalg.norm(camera_pos)) camera_up = np.cross(apostate[:3], apostate[-3:]) camera_up = camera_up/np.linalg.norm(camera_up) parallel_projection = True if view_from_orbit_normal: # to do this we need to get the position of apoapsis and the # orbit normal rlist = [np.linalg.norm(p) for p in np.transpose(poslist)] apoidx = np.argmax(rlist) apostate = spice.spkezr('MAVEN', etlist[apoidx], 'MAVEN_MSO', 'NONE', 'MARS')[0] camera_up = apostate[:3] camera_up = camera_up/np.linalg.norm(camera_up) camera_pos = np.cross(apostate[:3], apostate[-3:]) camera_pos = 5 * (camera_pos/np.linalg.norm(camera_pos)) parallel_projection = True # construct an orthonormal coordinate system camera_pos = np.array(camera_pos) camera_pos_norm = camera_pos/np.linalg.norm(camera_pos) camera_up = (camera_up - camera_pos_norm*np.dot(camera_pos_norm, camera_up)) camera_up = camera_up/np.linalg.norm(camera_up) camera_right = np.cross(-camera_pos_norm, camera_up) # set location of camera and orthogonal projection camera = mlab.gcf().scene.camera if parallel_projection: camera_pos = 5*camera_pos_norm camera.parallel_projection = True camera.parallel_scale = extent # half box size else: # TODO: this results in goofy images, fix this camera.parallel_projection = False camera.view_angle = 50 camera.position = np.array(camera_pos) camera.focal_point = (0, 0, 0) camera.view_up = camera_up camera.clipping_range = (0.01, 5000) # # Set up lighting # # The only light is the Sun, which is fixed on the MSO +x axis. # VTK's default lights are uniform and don't fall off with # distance, which is what we want mfig.scene.light_manager.light_mode = "vtk" sun = mfig.scene.light_manager.lights[0] sun.activate = True sun_vec = (1, 0, 0) # The only way to set a light in mayavi/vtk is with respect to the # camera position. This means we have to get elevation/azimuth # coordinates for the Sun with respect to the camera, which could # be anywhere. # Here's how the coordinate system is defined: # elevation: # [-90 -- +90] # +90 places the light along the direction of camera_up # azimuth: # [-180 -- +180], # +90 is in the plane of camera_up and camera_right. # +/-180 is behind, pointing at the camera # -90 places light to the left # so, to get elevation we need to put the sun in scene coordinates sun_scene = np.matmul([camera_right, camera_up, camera_pos_norm], sun_vec) # elevation is the angle is latitude measured wrt the y-axis of # the scene sun_elevation = np.rad2deg(np.arcsin(np.dot(sun_scene, [0, 1, 0]))) # azimuth is the angle in the x-z plane, clockwise from the z-axis sun_azimuth = np.rad2deg(np.arctan2(sun_scene[0], sun_scene[2])) # now we can set the location of the light, computed to always lie # along MSO+x sun.azimuth = sun_azimuth sun.elevation = sun_elevation # set the brightness of the Sun based on the ambient lighting of # Mars so there is no washing out sun.intensity = 1.0 - mars.property.ambient # # Render the 3D scene # mfig.scene.disable_render = False # mfig.scene.anti_aliasing_frames = 0 # can uncomment to make # # rendering faster and uglier mlab.show() mode = 'rgba' if transparent_background else 'rgb' img = mlab.screenshot(mode=mode, antialiased=True) mlab.close(all=True) # 3D stuff ends here # # Draw text and labels in matplotlib # fig, ax = plt.subplots(1, 1, dpi=400*mlab_pix/1000, figsize=(2.5, 2.5)) ax.imshow(img) # put an arrow along the orbit direction if show_orbit: arrow_width = 5 arrow_length = 1.5*arrow_width # by default, draw the arrow at the closest point on the orbit # to the viewer arrowidx = np.argmax([np.dot(camera_pos_norm, p) for p in np.transpose(poslist)]) if view_from_periapsis: # draw the arrow 45 degrees after periapsis arrowidx = np.argmax( [np.dot( (camera_right + camera_pos_norm)/np.sqrt(2), p) for p in np.transpose(poslist)]) if view_from_orbit_normal: # draw the arrow 45 degrees after periapsis arrowidx = np.argmax( [np.dot( (camera_right-camera_up)/np.sqrt(2.), p) for p in np.transpose(poslist)]) arrowetlist = etlist[arrowidx] + 5*60*np.array([0, 1]) arrowstatelist = spice.spkezr('MAVEN', arrowetlist, 'MAVEN_MSO', 'NONE', 'MARS')[0] arrowdir = arrowstatelist[1][:3] - arrowstatelist[0][:3] arrowdirproj = [np.dot(camera_right, arrowdir), np.dot(camera_up, arrowdir)] arrowdirproj = arrowdirproj/np.linalg.norm(arrowdirproj) arrowloc = np.transpose(poslist)[arrowidx] arrowlocproj = np.array([np.dot(camera_right, arrowloc), np.dot(camera_up, arrowloc)]) arrowlocdisp = (arrowlocproj + extent)/extent/2 arrow = ax.annotate('', xytext=(arrowlocdisp - 0.05*arrowdirproj), xy=(arrowlocdisp + 0.05*arrowdirproj), xycoords='axes fraction', textcoords='axes fraction', arrowprops=dict(facecolor=orbitcolor, edgecolor='none', width=0, headwidth=arrow_width, headlength=arrow_length)) # label the poles if view_from_periapsis: label_poles = True if view_from_orbit_normal: label_poles = True if label_poles is None: label_poles = False if label_poles: # label the north and south pole if they are visible def label_pole(loc, lbl): polepos = np.matmul(rmat, loc) poleposproj = np.array([np.dot(camera_right, polepos), np.dot(camera_up, polepos)]) poleposdisp = (poleposproj+extent)/extent/2 # determine if the north pole is visible polevis = (not (np.linalg.norm([poleposproj]) < 1 and np.dot(camera_pos, polepos) < 0)) if polevis: polelabel = ax.text(*poleposdisp, lbl, transform=ax.transAxes, color='#888888', ha='center', va='center', size=4, zorder=1) # outline the letter polelabel.set_path_effects([ path_effects.withStroke(linewidth=0.75, foreground='k')]) label_pole([0, 0, 1], 'N') label_pole([0, 0, -1], 'S') if show_orbit: # add a mark for periapsis and apoapsis rlist = [np.linalg.norm(p) for p in np.transpose(poslist)] # find periapsis/apoapsis def label_apsis(apsis_fn, label, **kwargs): apsisidx = apsis_fn(rlist) apsispos = np.transpose(poslist)[apsisidx] apsisposproj = np.array([np.dot(camera_right, apsispos), np.dot(camera_up, apsispos)]) apsisposdisp = (apsisposproj + extent)/extent/2 apsisvis = (not (np.linalg.norm([apsisposproj]) < 1 and np.dot(camera_pos, apsispos) < 0)) if apsisvis: apsis = mpatches.CirclePolygon(apsisposdisp, 0.015, resolution=4, transform=ax.transAxes, fc=orbitcolor, lw=0, zorder=10) ax.add_patch(apsis) ax.text(*apsisposdisp, label, transform=ax.transAxes, color='k', ha='center', size=4, zorder=10, **kwargs) label_apsis(np.argmin, 'P', va='center_baseline') label_apsis(np.argmax, 'A', va='center') if show_maven: # add a dot for the spacecraft location mavenpos = spice.spkezr('MAVEN', myet, 'MAVEN_MSO', 'NONE', 'MARS')[0][:3]/mars_radius mavenposproj = np.array([np.dot(camera_right, mavenpos), np.dot(camera_up, mavenpos)]) mavenposdisp = (mavenposproj + extent)/extent/2 mavenvis = (not (np.linalg.norm([mavenposproj]) < 1 and np.dot(camera_pos, mavenpos) < 0)) if mavenvis: maven = mpatches.Circle(mavenposdisp, 0.012, transform=ax.transAxes, fc=orbitcolor, lw=0, zorder=11) ax.add_patch(maven) ax.text(*mavenposdisp, 'M', transform=ax.transAxes, color='k', ha='center', va='center_baseline', size=4, zorder=11) # suppress all whitespace around the plot plt.subplots_adjust(top=1, bottom=0, right=1, left=0, hspace=0, wspace=0) plt.margins(0, 0) ax.set_axis_off() ax.xaxis.set_major_locator(plt.NullLocator()) ax.yaxis.set_major_locator(plt.NullLocator()) fig.canvas.draw() rgb_array = fig2rgb_array(fig) if not show: plt.close(fig) return_coords = {'extent': extent, 'scale': '3395 km', 'camera_pos': camera_pos, 'camera_pos_norm': camera_pos_norm, 'camera_up': camera_up, 'camera_right': camera_right, 'orbit_coords': poslist} return rgb_array, return_coords
def ADCS(RNV, e_Sun2Sat_b, e_Sat_b, observedAngular, r_Sat_i, dt_thrust): # This function reads vector information from sensors to determine the # attitude and angular velocities of the satellite. Data is also read from # the orbitData function results to determine the desired attitude at any # given time. Once the observed attitude is calculated, the thruster # commands are calculated and then sent to the thruster block # inputs: # - 3x3 Radial-Normal-Vel orientation matrix # - 3x1 Sun to Satellite unit vector in the body frame # - 3x1 Satellite position unit vector in the body frame # - 3x1 Angular Velocity vector # - 3x1 Satellite position vector, inertial frame # - scalar value for thruster duration # # outputs 1x20 array of: # - 4x4 binary thruster command # - scalar thrust duration # - 1x3 Roll-Pitch-Yaw angles in degrees # # note that it outputs a list with arrays within the list # define maximum and minimum errors thrustCommand = np.zeros([4, 4]) b_orient = np.zeros([3, 3]) i_orient = np.zeros([3, 3]) negativeAngle = -2 # deg positiveAngle = 2 # deg negativeRate = -.015 # deg/s positiveRate = .015 # deg/s au = cn.au r_Sun_i = au * np.array([0, 1, 0]) r_Sat_i = -np.matrix.transpose(r_Sat_i) r_Sun2Sat_i = r_Sun_i - r_Sat_i thrust_duration = dt_thrust # ++++++++++++++++++ TRIAD +++++++++++++++++++++ # r_Sun_r = np.matmul(RNV, r_Sun_i) e_Sun_r = r_Sun_r / np.linalg.norm(r_Sun_r) r_Sun2Sat_r = np.matmul(RNV, r_Sun2Sat_i) e_Sun2Sat_r = r_Sun2Sat_r / np.linalg.norm(r_Sun2Sat_r) r_Sat_r = np.matmul(RNV, r_Sat_i) e_Sat_r = r_Sat_r / np.linalg.norm(r_Sat_r) # algorithm x_b = e_Sat_b[:, 0] z_b = np.cross(x_b, e_Sun2Sat_b, axisa=0, axisb=0, axisc=0) / np.linalg.norm( np.cross(x_b, e_Sun2Sat_b, axisa=0, axisb=0, axisc=0)) y_b = np.cross(z_b, x_b, axisa=0, axisb=0) x_r = e_Sat_r z_r = np.cross(x_r, e_Sun2Sat_r) / np.linalg.norm( np.cross(x_r, e_Sun2Sat_r)) y_r = np.cross(z_r, x_r) # print(x_b.shape) b_orient[:, 0] = np.array([x_b[0, 0], x_b[1, 0], x_b[2, 0]]) b_orient[:, 1] = y_b b_orient[:, 2] = np.array([z_b[0, 0], z_b[1, 0], z_b[2, 0]]) i_orient[:, 0] = x_r i_orient[:, 1] = y_r i_orient[:, 2] = z_r T1 = np.matmul(b_orient, np.matrix.transpose(i_orient)) T2 = np.matrix.transpose(T1) T2_copy = T2.copy() # make non-strided somehow RPY = spice.m2eul(T2_copy, 1, 2, 3) RPY = np.array(RPY) # Roll, Pitch, Yaw tuple to numpy array RPY = RPY * 180 / np.pi # =============== Error Correction ================= # if RPY[0] < negativeAngle: # Roll error correction thrustCommand[0, 1] = 1 thrustCommand[2, 1] = 1 elif RPY[0] > positiveAngle: thrustCommand[0, 3] = 1 thrustCommand[2, 3] = 1 if RPY[1] < negativeAngle: # Pitch error correction thrustCommand[1, 2] = 1 thrustCommand[3, 0] = 1 elif RPY[1] > positiveAngle: thrustCommand[1, 0] = 1 thrustCommand[3, 2] = 1 if RPY[2] < negativeAngle: # Yaw error correction thrustCommand[0, 0] = 1 thrustCommand[2, 2] = 1 elif RPY[2] > positiveAngle: thrustCommand[0, 2] = 1 thrustCommand[2, 0] = 1 # ================= RATE correction ================= # if observedAngular[0, 0] > positiveRate: # Roll rate correction thrustCommand[0, 3] = 1 thrustCommand[2, 3] = 1 elif observedAngular[0, 0] < negativeRate: thrustCommand[0, 1] = 1 thrustCommand[2, 1] = 1 if observedAngular[1, 0] > positiveRate: # Pitch rate correction thrustCommand[1, 0] = 1 thrustCommand[3, 2] = 1 elif observedAngular[1, 0] < negativeRate: thrustCommand[1, 2] = 1 thrustCommand[3, 0] = 1 if observedAngular[2, 0] > positiveRate: # Yaw rate correction thrustCommand[0, 2] = 1 thrustCommand[2, 0] = 1 elif observedAngular[2, 0] < negativeRate: thrustCommand[0, 0] = 1 thrustCommand[2, 2] = 1 t_command_t_duration_RPY = [thrustCommand[0, :], thrustCommand[1, :], \ thrustCommand[2, :], thrustCommand[3, :], thrust_duration, RPY] return t_command_t_duration_RPY