Esempio n. 1
0
    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
Esempio n. 2
0
    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
Esempio n. 3
0
    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)
Esempio n. 4
0
    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
}
Esempio n. 5
0
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
Esempio n. 6
0
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
Esempio n. 7
0
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