コード例 #1
0
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
コード例 #2
0
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
コード例 #3
0
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
コード例 #4
0
ファイル: lro_drivers.py プロジェクト: amystamile-usgs/ale
    def spacecraft_direction(self):
        """
        Returns the x axis of the first velocity vector relative to the
        spacecraft. This indicates of the craft is moving forwards or backwards.

        From LROC Frame Kernel: lro_frames_2014049_v01.tf
        "+X axis is in the direction of the velocity vector half the year. The
        other half of the year, the +X axis is opposite the velocity vector"

        Hence we rotate the first velocity vector into the sensor reference
        frame, but the X component of that vector is inverted compared to the
        spacecraft so a +X indicates backwards and -X indicates forwards

        The returned velocity is also slightly off from the spacecraft velocity
        due to the sensor being attached to the craft with wax.

        Returns
        -------
        direction : double
                    X value of the first velocity relative to the sensor
        """
        frame_chain = self.frame_chain
        lro_bus_id = spice.bods2c('LRO_SC_BUS')
        time = self.ephemeris_start_time
        state, _ = spice.spkezr(self.spacecraft_name, time, 'J2000', 'None',
                                self.target_name)
        position = state[:3]
        velocity = state[3:]
        rotation = frame_chain.compute_rotation(1, lro_bus_id)
        rotated_velocity = spice.mxv(rotation._rots.as_matrix()[0], velocity)
        return rotated_velocity[0]
コード例 #5
0
def search_stars(obsinfo, mag_limit=7.0):
    query = ("SELECT"
             " CATALOG_NUMBER,RA,DEC,VISUAL_MAGNITUDE,PARLAX,SPECTRAL_TYPE"
             " FROM HIPPARCOS")
    nmrows, _error, _errmsg = spice.ekfind(query)
    stars = []
    for row in range(nmrows):
        ra = spice.ekgd(1, row, 0)[0] * spice.rpd()
        dec = spice.ekgd(2, row, 0)[0] * spice.rpd()
        mag = spice.ekgd(3, row, 0)[0]
        vec = spice.radrec(1.0, ra, dec)
        tvec = spice.mxv(obsinfo.ref2obsmtx, vec)
        _tpa, tdist = vec_padist(obsinfo.center, tvec)
        if tdist < obsinfo.fov.fovmax and mag < mag_limit:
            parallax = spice.ekgd(4, row, 0)[0]
            spectral = spice.ekgc(5, row, 0)[0]
            distance = 1.0 / np.tan(parallax * spice.rpd())
            distance = spice.convrt(distance, "AU", "km")
            pos = tvec * distance
            vp = viewport_frustum(obsinfo.fov.bounds_rect, obsinfo.width,
                                  obsinfo.height, pos)
            star = {
                "hip_id": spice.ekgi(0, row, 0)[0],
                "position": tvec,
                "ra": ra,
                "dec": dec,
                "spectral_type": spectral,
                "visual_magnitude": mag,
                "distance": distance,
                "image_pos": vp[0:2],
                "color": get_star_color(spectral),
            }
            stars.append(star)
    return stars
コード例 #6
0
def get_geometry_info(obs2refmtx, fov, width, height):
    cvec = spice.vhat(fov.bounds_rect.center_vec)
    cvec_ref = spice.mxv(obs2refmtx, cvec)

    # Azimuth in the upward direction of the screen
    mvec = spice.vhat(fov.bounds_rect.top_vec)
    mvec_ref = spice.mxv(obs2refmtx, mvec)
    pa, dist = vec_padist(cvec_ref, mvec_ref)
    pos_angle = pa * spice.dpr()

    # Pixel resolution up to the center of the screen top edge
    vp = viewport_frustum(fov.bounds_rect, width, height, mvec)
    angle_res = dist / (height / 2.0 - vp[1]) * spice.dpr()
    _, ra, dec = spice.recrad(cvec_ref)

    return pos_angle, angle_res, ra * spice.dpr(), dec * spice.dpr()
コード例 #7
0
def conicaEspacio(Omega=0, i=0, omega=0, figsize=(4, 4)):
    #Propiedades de la cnica
    p = 1
    e = 0.7

    #Visual inicial
    A = 60
    h = 30

    #Puntos de la cnica en el sistema natural de referencia
    fs = np.linspace(0, 360 * GRADOS, 100)
    rs = p / (1 + e * np.cos(fs))
    xs = rs * np.cos(fs)
    ys = rs * np.sin(fs)
    zs = np.zeros_like(fs)

    #Vectores en el sistema original
    vecrs = np.array([[x, y, z] for x, y, z in zip(xs, ys, zs)])

    #Matriz de transformacin
    Omega *= GRADOS
    i *= GRADOS
    omega *= GRADOS

    #Ntese que en el caso de la cnica, el sistema natural
    #es el que se encuentra rotado respecto al sistema final
    #por eso la matriz es la inversa
    Rtot = spy.eul2m(-Omega, -i, -omega, 3, 1, 3)

    #Puntos transformados
    vecrppps = np.array([
        spy.mxv(Rtot, [vecrs[i, 0], vecrs[i, 1], vecrs[i, 2]])
        for i in range(len(fs))
    ])

    #Grafico
    fig = plt.figure(figsize=figsize)
    ax = fig.add_subplot(111, projection='3d')

    #Defino desde dnde voy a ver la cnica
    ax.view_init(h, A)

    #Grfica de los puntos
    ax.plot(vecrppps[:, 0], vecrppps[:, 1], vecrppps[:, 2], 'r-', lw=3)

    #Ejes originales
    vmax = 3
    ax.plot([0, vmax, 0], [0, 0, 0], [0, 0, 0], 'k-')
    ax.text(vmax, 0, 0, 'x')
    ax.plot([0, 0, 0], [0, vmax, 0], [0, 0, 0], 'k-')
    ax.text(0, vmax, 0, 'y')
    ax.plot([0, 0, 0], [0, 0, 0], [0, 0, vmax], 'k-')
    ax.text(0, 0, vmax, 'z')

    #Decoracin
    xl = ax.set_xlim((-vmax, vmax))
    yl = ax.set_ylim((-vmax, vmax))
    zl = ax.set_zlim((-vmax, vmax))
コード例 #8
0
def caps_crosstrack(tempdatetime, windspeed):
    et = spice.datetime2et(tempdatetime)

    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]

    spacecraft_axis = np.array([0, 0, 1])
    rotated_spacecraft_axis = spice.mxv(cmat, spacecraft_axis)
    # print("cmat", cmat)
    # print("rotated spacecraft axis",rotated_spacecraft_axis)
    ram_unit = spice.mxv(cmat, -ramdir)  # Ram Unit in SC coords
    # print("ram_unit",ram_unit)

    if windspeed < 0:
        rotationmatrix = spice.axisar(np.array([0, 0, -1]), 90 * spice.rpd())
    if windspeed > 0:
        rotationmatrix = spice.axisar(np.array([0, 0, -1]), -90 * spice.rpd())

    # print(rotationmatrix)
    crossvec = spice.mxv(
        rotationmatrix,
        ram_unit)  # Rotate ram unit to find crosstrack velocity vector
    # print("temp crossvec",crossvec)
    # print("vsep SC Frame",spice.vsep(ram_unit,crossvec)*spice.dpr())
    cmat_t = spice.xpose(cmat)
    crossvec_titan = spice.mxv(cmat_t,
                               crossvec)  # Transform back to IAU Titan Frame

    # print("crossvec", crossvec)
    # print("crossvec_titan", crossvec_titan, spice.unorm(crossvec_titan))
    # print("vsep titan frame", spice.vsep(ramdir, crossvec_titan) * spice.dpr())

    return crossvec_titan
コード例 #9
0
ファイル: lpw.py プロジェクト: irbdavid/maven
def ram_angles(time):
    """Return the angle between SC-Y and the ram direction (0. = Y to ram)"""
    p = spiceypy.spkezr('MAVEN', time, 'IAU_MARS', 'NONE', 'MARS')[0][3:]
    r = spiceypy.pxform('IAU_MARS', 'MAVEN_SPACECRAFT', time)
    a = spiceypy.mxv(r, p)

    e = np.arctan( np.sqrt(a[1]**2. + a[2]**2.) / a[0]) * 180./np.pi
    f = np.arctan( np.sqrt(a[0]**2. + a[2]**2.) / a[1]) * 180./np.pi
    g = np.arctan( np.sqrt(a[0]**2. + a[1]**2.) / a[2]) * 180./np.pi
    if e < 0.: e = e + 180.
    if f < 0.: f = f + 180.
    if g < 0.: g = g + 180.

    return np.array((e,f,g))
コード例 #10
0
ファイル: spice.py プロジェクト: RichardHaythorn/cassinipy
def caps_ramdirection_azielv(tempdatetime, observ='titan'):
    """
    Returns the azimuth and elevation of the ramdirection
    """
    ram_unit = cassini_ramdirection_SCframe(tempdatetime, observ=observ)

    sc2CAPS = np.array(([0, -1, 0],
                        [-1, 0, 0],
                        [0, 0, -1]))
    ram_unit_CAPS = spice.mxv(sc2CAPS, ram_unit)
    # print(ram_unit_CAPS)
    ram_unit_azielv = np.array(spice.reclat(ram_unit_CAPS)[1:]) * spice.dpr()

    return ram_unit_azielv[0], ram_unit_azielv[1]
コード例 #11
0
ファイル: spice.py プロジェクト: RichardHaythorn/cassinipy
def rotate_CAPS_SCframe(CAPS_actuation, instrument, anodes=False):
    """
    Returns the CAPS pointing vector after actuation in the SC frame
    """
    naifiddict = {'ims': -82820, 'els': -82821, 'ibs1': -82822, 'ibs2': -82823, 'ibs3': -82824}
    naifid = naifiddict[instrument]

    if not anodes:
        rotationmatrix = spice.spiceypy.axisar(np.array([0, 0, -1]), CAPS_actuation * spice.rpd())
        temp = spice.mxv(rotationmatrix, spice.spiceypy.getfov(naifid, 20)[2])

    if (instrument == 'ims' or instrument == 'els') and anodes == True:
        temp = []

        rotationmatrix_act = spice.spiceypy.axisar(np.array([0, 0, -1]), CAPS_actuation * spice.rpd())

        for anodenumber, x in enumerate(np.arange(70, -90, -20)):
            rotationmatrix_anode = spice.spiceypy.axisar(np.array([-1, 0, 0]), x * spice.rpd())
            postanode_rotation = spice.mxv(rotationmatrix_anode, spice.spiceypy.getfov(naifid, 20)[2])

            temp.append(spice.mxv(rotationmatrix_act, postanode_rotation))

    return temp
コード例 #12
0
ファイル: wrappers.py プロジェクト: zhaomingxian/3DCORE
def get_vector(frame_from: str, frame_to: str, vector: np.ndarray,
               time: datetime.datetime) -> np.ndarray:
    """
    Transform vector from one reference frame to another.

    :param frame_from: source frame
    :param frame_to: target frame
    :param vector: vector in source frame
    :param time: datetime
    :return: vector in target frame
    """
    if frame_from == frame_to:
        return vector

    times_et = spiceypy.datetime2et(time.replace(tzinfo=None))

    return spiceypy.mxv(spiceypy.pxform(frame_from, frame_to, times_et),
                        vector)
コード例 #13
0
ファイル: spice.py プロジェクト: RichardHaythorn/cassinipy
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
コード例 #14
0
    def getSpoint(self):

        self.bight_obs = []
        self.spoing = []
        #
        # calculation of the boresight vector and the intersection point
        #
        for et in self.time.window:

            mat_ins_obs = cspice.pxform(self.frame, self.host.frame, et)

            bsight_obs = cspice.mxv(mat_ins_obs, self.bsight_ins)
            spoint, trgepc, srfvec = cspice.sincpt('Ellipsoid',
                                                   self.target.name, et,
                                                   self.target.frame, 'NONE',
                                                   self.host.name,
                                                   self.host.frame, bsight_obs)

            self.bsight_obs.append(bsight_obs)
            self.spoint.append(spoint)

        return self.spoint
コード例 #15
0
    
    mx = sp.pxform('B1950', 'J2000', et[0]) # SPICE knows about 1950, but not 1900!
    
    ra_1950  = t['RA']
    dec_1950 = t['Dec']
        
    ra_2000  = []
    dec_2000 = []
    pt_1950  = []
    
    # Loop over every star and precess it individually. This loop is a real bottleneck.
    
    for i in range(num_stars):
        
        pt_1900 = sp.radrec(1, ra_1950[i], dec_1950[i])
        pt_1950 = sp.mxv(mx, pt_1900)
        pt_2000 = sp.mxv(mx, pt_1950) # SPICE will not precess 1900 -> 2000. So we apply 1950 -> 2000, do it 2x.
        d, ra_2000_i, dec_2000_i = sp.recrad(pt_2000)
        dec_2000.append(dec_2000_i)
        ra_2000.append(ra_2000_i)  
      
    hd['RA_2000']  = ra_2000
    hd['Dec_2000'] = dec_2000
    
    # Now save this pickle file, so we never have to run this routine again
    
    lun = open(file_hd_pickle, 'wb')
    pickle.dump(hd, lun)
    lun.close()
    print("Wrote: " + file_hd_pickle)
コード例 #16
0
ファイル: check_dsk.py プロジェクト: esaSPICEservice/spirec
def plot_solar_ilum(utc, mk, dsk, observer, target, target_frame, pixels=150):
    mpl.rcParams['figure.figsize'] = (26.0, 26.0)

    spiceypy.furnsh(mk)

    utcstr = utc[10:13] + utc[14:16] + utc[17:19]
    et = spiceypy.utc2et(utc)

    spiceypy.furnsh(dsk)

    nx, ny = (pixels, pixels)  # resolution of the image
    x = np.linspace(-5, 5, nx)
    y = np.linspace(-5, 5, ny)
    xv, yv = np.meshgrid(x, y)

    phase_matrix = np.zeros((nx, ny))
    emissn_matrix = np.zeros((nx, ny))
    solar_matrix = np.zeros((nx, ny))

    isvisible, isiluminated = [], []
    r, lt = spiceypy.spkpos(observer, et, 'J2000', 'NONE', target)

    #
    # We define a 'Nadir frame' w.r.t. J000 to make it general regardless of
    #
    #
    zN = r
    zN = zN / np.linalg.norm(zN)
    xN = np.array(
        [1, 0, 0]) - np.dot(np.dot([1, 0, 0], zN), zN) / np.linalg.norm(zN)**2
    xN = xN / np.linalg.norm(xN)
    yN = np.cross(zN, xN)
    yN = yN / np.linalg.norm(yN)
    RotM = np.linalg.inv(np.array([xN, yN, zN]))
    spoints = []
    for i, x in enumerate(xv):
        for j, y in enumerate(yv):
            dpxy = [x[i], y[i], -np.linalg.norm(r) * 1000]
            ibsight = spiceypy.mxv(RotM, dpxy)
            # ibsight = [x[i], y[j], -r*1000]
            try:
                (spoint, trgepc,
                 srfvec) = spiceypy.sincpt('DSK/UNPRIORITIZED', target, et,
                                           target_frame, 'NONE', observer,
                                           'J2000', ibsight)
                spoints.append(spoint)
                (trgepc, srfvec, phase, solar, emissn, visiblef,
                 iluminatedf) = spiceypy.illumf('DSK/UNPRIORITIZED', target,
                                                'SUN', et, target_frame,
                                                'NONE', observer, spoint)
                #check visibility of spoint
                if visiblef == True:
                    isvisible.append(visiblef)
                if iluminatedf == True:
                    isiluminated.append(iluminatedf)
                    if solar > np.pi / 2:
                        solar_matrix[i, j] = np.pi - solar
                    else:
                        solar_matrix[i, j] = solar
                else:
                    solar_matrix[i, j] = np.pi / 2  # not illuminated
                emissn_matrix[i, j] = emissn
                phase_matrix[i, j] = phase
            except:
                pass
                emissn_matrix[i, j] = 0
                phase_matrix[i, j] = math.pi
                solar_matrix[i, j] = np.pi / 2

    spoints = np.asarray(spoints)
    fig = plt.figure(figsize=(9, 9))
    ax = fig.add_subplot(111, projection='3d')
    ax.scatter(spoints[:, 0], spoints[:, 1], spoints[:, 2], marker='.')
    plt.xlabel("x position")
    plt.ylabel("y position")
    plt.title('')
    plt.axis('equal')
    plt.show()

    print('total number of points: ', pixels * pixels)
    print('occulted points: ', pixels * pixels - len(isvisible))
    print('not iluminated points: ', pixels * pixels - len(isiluminated))

    name = 'solar_matrix'
    try:
        os.remove(name)
        print('modifying png file' + name)
    except:
        print('generating png file' + name)
    plt.imshow(solar_matrix, cmap='viridis_r')
    plt.show()
    imsave(name + utcstr + '.png', solar_matrix)

    return
コード例 #17
0
def compute_backplanes(file,
                       name_target,
                       frame,
                       name_observer,
                       angle1=0,
                       angle2=0,
                       angle3=0):
    """
    Returns a set of backplanes for a single specified image. The image must have WCS coords available in its header.
    Backplanes include navigation info for every pixel, including RA, Dec, Eq Lon, Phase, etc.
    
    The results are returned to memory, and not written to a file.
    
    SPICE kernels must be alreaded loaded, and spiceypy running.
    
    Parameters
    ----
    
    file:
        String. Input filename, for FITS file.
    frame:
        String. Reference frame of the target body. 'IAU_JUPITER', 'IAU_MU69', '2014_MU69_SUNFLOWER_ROT', etc.
        This is the frame that the Radius_eq and Longitude_eq are computed in.
    name_target:
        String. Name of the central body. All geometry is referenced relative to this (e.g., radius, azimuth, etc)
    name_observer:
        String. Name of the observer. Must be a SPICE body name (e.g., 'New Horizons')
    
    Optional Parameters
    ----
    
    angle{1,2,3}:
        **NOT REALLY TESTED. THE BETTER WAY TO CHANGE THE ROTATION IS TO USE A DIFFERENT FRAME.**

        Rotation angles which are applied when defining the plane in space that the backplane will be generated for.
        These are applied in the order 1, 2, 3. Angles are in radians. Nominal values are 0.
       
        This allows the simulation of (e.g.) a ring system inclined relative to the nominal body equatorial plane.
        
        For MU69 sunflower rings, the following descriptions are roughly accurate, becuase the +Y axis points
        sunward, which is *almost* toward the observer. But it is better to experiment and find the 
        appropriate angle that way, than rely on this ad hoc description. These are close for starting with.
 
                      - `angle1` = Tilt front-back, from face-on. Or rotation angle, if tilted right-left.
                      - `angle2` = Rotation angle, if tilted front-back. 
                      - `angle3` = Tilt right-left, from face-on.
    
    do_fast:
        Boolean. If set, generate only an abbreviated set of backplanes. **NOT CURRENTLY IMPLEMENTED.**
        
    Output
    ----

    Output is a tuple, consisting of each of the backplanes, and a text description for each one. 
    The size of each of these arrays is the same as the input image.
    
    The position of each of these is the plane defined by the target body, and the normal vector to the observer.
    
    output = (backplanes, descs)
    
        backplanes = (ra,      dec,      radius_eq,      longitude_eq,      phase)
        descs      = (desc_ra, desc_dec, desc_radius_eq, desc_longitude_eq, desc_phase)
    
    Radius_eq:
        Radius, in the ring's equatorial plane, in km
    Longitude_eq:
        Longitude, in the equatorial plane, in radians (0 .. 2pi)
    RA:
        RA of pixel, in radians
    Dec:
        Dec of pixel, in
    dRA:
        Projected offset in RA  direction between center of body (or barycenter) and pixel, in km.
    dDec:
        Projected offset in Dec direction between center of body (or barycenter) and pixel, in km.
        
    Z:  
        Vertical value of the ring system.
        
    With special options selected (TBD), then additional backplanes will be generated -- e.g., a set of planes
    for each of the Jovian satellites in the image, or sunflower orbit, etc.
    
    No new FITS file is written. The only output is the returned tuple.
    
    """

    if not (frame):
        raise ValueError('frame undefined')

    if not (name_target):
        raise ValueError('name_target undefined')

    if not (name_observer):
        raise ValueError('name_observer undefined')

    name_body = name_target  # Sometimes we use one, sometimes the other. Both are identical

    fov_lorri = 0.3 * hbt.d2r

    abcorr = 'LT'

    do_satellites = False  # Flag: Do we create an additional backplane for each of Jupiter's small sats?

    # Open the FITS file

    w = WCS(
        file
    )  # Warning: I have gotten a segfault here before if passing a FITS file with no WCS info.
    hdulist = fits.open(file)

    et = float(hdulist[0].header['SPCSCET'])  # ET of mid-exposure, on s/c
    n_dx = int(hdulist[0].header['NAXIS1']
               )  # Pixel dimensions of image. Both LORRI and MVIC have this.
    n_dy = int(hdulist[0].header['NAXIS2'])

    hdulist.close()

    # Setup the output arrays

    lon_arr = np.zeros(
        (n_dy, n_dx))  # Longitude of pixel (defined with recpgr)
    lat_arr = np.zeros(
        (n_dy, n_dx))  # Latitude of pixel (which is zero, so meaningless)
    radius_arr = np.zeros((n_dy, n_dx))  # Radius, in km
    altitude_arr = np.zeros((n_dy, n_dx))  # Altitude above midplane, in km
    ra_arr = np.zeros((n_dy, n_dx))  # RA of pixel
    dec_arr = np.zeros((n_dy, n_dx))  # Dec of pixel
    dra_arr = np.zeros(
        (n_dy, n_dx)
    )  # dRA  of pixel: Distance in sky plane between pixel and body, in km.
    ddec_arr = np.zeros(
        (n_dy, n_dx)
    )  # dDec of pixel: Distance in sky plane between pixel and body, in km.
    phase_arr = np.zeros((n_dy, n_dx))  # Phase angle
    x_arr = np.zeros(
        (n_dy, n_dx))  # Intersection of sky plane: X pos in bdoy coords
    y_arr = np.zeros(
        (n_dy, n_dx))  # Intersection of sky plane: X pos in bdoy coords
    z_arr = np.zeros(
        (n_dy, n_dx))  # Intersection of sky plane: X pos in bdoy coords

    # =============================================================================
    #  Do the backplane, in the general case.
    #  This is a long routine, because we deal with all the satellites, the J-ring, etc.
    # =============================================================================

    if (True):

        # Look up body parameters, used for PGRREC().

        (num, radii) = sp.bodvrd(name_target, 'RADII', 3)

        r_e = radii[0]
        r_p = radii[2]
        flat = (r_e - r_p) / r_e

        # Define a SPICE 'plane' along the plane of the ring.
        # Do this in coordinate frame of the body (IAU_JUPITER, 2014_MU69_SUNFLOWER_ROT, etc).

        # =============================================================================
        # Set up the Jupiter system specifics
        # =============================================================================

        if (name_target.upper() == 'JUPITER'):
            plane_target_eq = sp.nvp2pl(
                [0, 0, 1],
                [0, 0, 0
                 ])  # nvp2pl: Normal Vec + Point to Plane. Jupiter north pole?

            # For Jupiter only, define a few more output arrays for the final backplane set

            ang_metis_arr = np.zeros(
                (n_dy, n_dx))  # Angle from pixel to body, in radians
            ang_adrastea_arr = ang_metis_arr.copy()
            ang_thebe_arr = ang_metis_arr.copy()
            ang_amalthea_arr = ang_metis_arr.copy()

# =============================================================================
# Set up the MU69 specifics
# =============================================================================

        if ('MU69' in name_target.upper()):

            # Define a plane, which is the plane of sunflower rings (ie, X-Z plane in Sunflower frame)
            # If additional angles are passed, then create an Euler matrix which will do additional angles of rotation.
            # This is defined in the 'MU69_SUNFLOWER' frame

            vec_plane = [0, 1,
                         0]  # Use +Y (anti-sun dir), which is normal to XZ.
            #            vec_plane = [0, 0, 1]                                 # Use +Y (anti-sun dir), which is normal to XZ.
            plane_target_eq = sp.nvp2pl(
                vec_plane,
                [0, 0, 0])  # "Normal Vec + Point to Plane". 0,0,0 = origin.

        # XXX NB: This plane in in body coords, not J2K coords. This is what we want, because the
        # target intercept calculation is also done in body coords.

# =============================================================================
# Set up the various output planes and arrays necessary for computation
# =============================================================================

# Get xformation matrix from J2K to target system coords. I can use this for points *or* vectors.

        mx_j2k_frame = sp.pxform('J2000', frame, et)  # from, to, et

        # Get vec from body to s/c, in both body frame, and J2K.
        # NB: The suffix _j2k indicates j2K frame. _frame indicates the frame of target (IAU_JUP, MU69_SUNFLOWER, etc)

        (st_target_sc_frame, lt) = sp.spkezr(name_observer, et, frame, abcorr,
                                             name_target)
        (st_sc_target_frame, lt) = sp.spkezr(name_target, et, frame, abcorr,
                                             name_observer)
        (st_target_sc_j2k, lt) = sp.spkezr(name_observer, et, 'J2000', abcorr,
                                           name_target)
        (st_sc_target_j2k, lt) = sp.spkezr(name_target, et, 'J2000', abcorr,
                                           name_observer)

        vec_target_sc_frame = st_target_sc_frame[0:3]
        vec_sc_target_frame = st_sc_target_frame[0:3]
        vec_target_sc_j2k = st_target_sc_j2k[0:3]
        vec_sc_target_j2k = st_sc_target_j2k[0:3]

        dist_target_sc = sp.vnorm(
            vec_target_sc_j2k)  # Get target distance, in km

        # vec_sc_target_frame = -vec_target_sc_frame # ACTUALLY THIS IS NOT TRUE!! ONLY TRUE IF ABCORR=NONE.

        # Name this vector a 'point'. INRYPL requires a point argument.

        pt_target_sc_frame = vec_target_sc_frame

        # Look up RA and Dec of target (from sc), in J2K

        (_, ra_sc_target, dec_sc_target) = sp.recrad(vec_sc_target_j2k)

        # Get vector from target to sun. We use this later for phase angle.

        (st_target_sun_frame,
         lt) = sp.spkezr('Sun', et, frame, abcorr,
                         name_target)  # From body to Sun, in body frame
        vec_target_sun_frame = st_target_sun_frame[0:3]

        # Create a 2D array of RA and Dec points
        # These are made by WCS, so they are guaranteed to be right.

        xs = range(n_dx)
        ys = range(n_dy)
        (i_x_2d, i_y_2d) = np.meshgrid(xs, ys)
        (ra_arr, dec_arr) = w.wcs_pix2world(i_x_2d, i_y_2d,
                                            False)  # Returns in degrees
        ra_arr *= hbt.d2r  # Convert to radians
        dec_arr *= hbt.d2r

        # Compute the projected distance from MU69, in the sky plane, in km, for each pixel.
        # dist_target_sc is the distance to MU69, and we use this to convert from radians, to km.
        # 16-Oct-2018. I had been computing this erroneously. It should be *cosdec, not /cosdec.

        dra_arr = (ra_arr - ra_sc_target) * dist_target_sc * np.cos(dec_arr)
        ddec_arr = (dec_arr - dec_sc_target) * dist_target_sc  # Convert to km

        # =============================================================================
        #  Compute position for additional Jupiter bodies, as needed
        # =============================================================================

        if (name_target.upper() == 'JUPITER'):
            vec_metis_j2k, lt = sp.spkezr('Metis', et, 'J2000', abcorr,
                                          'New Horizons')
            vec_adrastea_j2k, lt = sp.spkezr('Adrastea', et, 'J2000', abcorr,
                                             'New Horizons')
            vec_thebe_j2k, lt = sp.spkezr('Thebe', et, 'J2000', abcorr,
                                          'New Horizons')
            vec_amalthea_j2k, lt = sp.spkezr('Amalthea', et, 'J2000', abcorr,
                                             'New Horizons')

            vec_metis_j2k = np.array(vec_metis_j2k[0:3])
            vec_thebe_j2k = np.array(vec_thebe_j2k[0:3])
            vec_adrastea_j2k = np.array(vec_adrastea_j2k[0:3])
            vec_amalthea_j2k = np.array(vec_amalthea_j2k[0:3])

# =============================================================================
# Loop over pixels in the output image
# =============================================================================

        for i_x in xs:
            for i_y in ys:

                # Look up the vector direction of this single pixel, which is defined by an RA and Dec
                # Vector is thru pixel to ring, in J2K.
                # RA and Dec grids are made by WCS, so they are guaranteed to be right.

                vec_pix_j2k = sp.radrec(1., ra_arr[i_y, i_x], dec_arr[i_y,
                                                                      i_x])

                # Convert vector along the pixel direction, from J2K into the target body frame

                vec_pix_frame = sp.mxv(mx_j2k_frame, vec_pix_j2k)

                # And calculate the intercept point between this vector, and the ring plane.
                # All these are in body coordinates.
                # plane_target_eq is defined as the body's equatorial plane (its XZ for MU69).

                # ** Some question as to whether we should shoot this vector at the ring plane, or the sky plane.
                # Ring plane is normally the one we want. But, for the case of edge-on rings, the eq's break down.
                # So, we should use the sky plane instead. Testing shows that for the case of MU69 Eq's break down
                # for edge-on rings... there is always an ambiguity.

                # ** For testing, try intersecting the sky plane instead of the ring plane.
                # ** Confirmed: Using sky plane gives identical results in case of face-on rings.
                #    And it gives meaningful results in case of edge-on rings, where ring plane did not.
                #    However, for normal rings (e.g., Jupiter), we should continue using the ring plane, not sky plane.

                do_sky_plane = True  # For ORT4, where we want to use euler angles, need to set this to False

                if do_sky_plane and ('MU69' in name_target):
                    plane_sky_frame = sp.nvp2pl(
                        vec_sc_target_frame,
                        [0, 0, 0])  # Frame normal to s/c vec, cntrd on MU69
                    (npts,
                     pt_intersect_frame) = sp.inrypl(pt_target_sc_frame,
                                                     vec_pix_frame,
                                                     plane_sky_frame)

                    # pt_intersect_frame is the point where the ray hits the skyplane, in the coordinate frame
                    # of the target body.

                else:  # Calc intersect into equator of target plane (ie, ring plane)
                    (npts,
                     pt_intersect_frame) = sp.inrypl(pt_target_sc_frame,
                                                     vec_pix_frame,
                                                     plane_target_eq)
                    # pt, vec, plane

                # Swap axes in target frame if needed.
                # In the case of MU69 (both sunflower and tunacan), the frame is defined s.t. the ring
                # is in the XZ plane, not XY. This is strange (but correct).
                # I bet MU69 is the only ring like this. Swap it so that Z means 'vertical, out of plane' --
                # that is, put it into normal XYZ rectangular coords, so we can use RECLAT etc on it.

                if (
                        'MU69' in name_target
                ):  # Was 0 2 1. But this makes tunacan radius look in wrong dir.
                    # 201 looks same
                    # 210 similar
                    # 201 similar
                    # 102, 120 similar.
                    # ** None of these change orientation of 'radius' backplane. OK.

                    pt_intersect_frame = np.array([
                        pt_intersect_frame[0], pt_intersect_frame[2],
                        pt_intersect_frame[1]
                    ])

                # Get the radius and azimuth of the intersect, in the ring plane
                # Q: Why for the TUNACAN is the radius zero here along horizontal (see plot)?
                # A: Ahh, it is not zero. It is just that the 'projected radius' of a ring that is nearly edge-on
                # can be huge! Basically, if we try to calc the intersection with that plane, it will give screwy
                # answers, because the plane is so close to edge-on that intersection could be a long way
                # from body itself.

                # Instead, I really want to take the tangent sky plane, intersect that, and then calc the
                # position of that (in xyz, radius, longitude, etc).
                # Since that plane is fixed, I don't see a disadvantage to doing that.

                # We want the 'radius' to be the radius in the equatorial plane -- that is, sqrt(x^2 + y^2).
                # We don't want it to be the 'SPICE radius', which is the distance.
                # (For MU69 equatorial plane is nominally XZ, but we have already changed that above to XY.)

                _radius_3d, lon, lat = sp.reclat(pt_intersect_frame)

                radius_eq = sp.vnorm(
                    [pt_intersect_frame[0], pt_intersect_frame[1], 0])
                #                radius_eq = sp.vnorm([pt_intersect_frame[0], pt_intersect_frame[1], pt_intersect_frame[2]])

                # Get the vertical position (altitude)

                altitude = pt_intersect_frame[2]

                # Calculate the phase angle: angle between s/c-to-ring, and ring-to-sun

                vec_ring_sun_frame = -pt_intersect_frame + vec_target_sun_frame

                angle_phase = sp.vsep(-vec_pix_frame, vec_ring_sun_frame)

                # Save various derived quantities

                radius_arr[i_y, i_x] = radius_eq
                lon_arr[i_y, i_x] = lon
                phase_arr[i_y, i_x] = angle_phase
                altitude_arr[i_y, i_x] = altitude

                # Save these just for debugging

                x_arr[i_y, i_x] = pt_intersect_frame[0]
                y_arr[i_y, i_x] = pt_intersect_frame[1]
                z_arr[i_y, i_x] = pt_intersect_frame[2]

                # Now calc angular separation between this pixel, and the satellites in our list
                # Since these are huge arrays, cast into floats to make sure they are not doubles.

                if (name_body.upper() == 'JUPITER'):
                    ang_thebe_arr[i_y, i_x] = sp.vsep(vec_pix_j2k,
                                                      vec_thebe_j2k)
                    ang_adrastea_arr[i_y,
                                     i_x] = sp.vsep(vec_pix_j2k,
                                                    vec_adrastea_j2k)
                    ang_metis_arr[i_y, i_x] = sp.vsep(vec_pix_j2k,
                                                      vec_metis_j2k)
                    ang_amalthea_arr[i_y,
                                     i_x] = sp.vsep(vec_pix_j2k,
                                                    vec_amalthea_j2k)

        # Now, fix a bug. The issue is that SP.INRYPL uses the actual location of the bodies (no aberration),
        # while their position is calculated (as it should be) with abcorr=LT. This causes a small error in the
        # positions based on the INRYPL calculation. This should probably be fixed above, but it was not
        # obvious how. So, instead, I am fixing it here, by doing a small manual offset.

        # Calculate the shift required, by measuring the position of MU69 with abcorr=NONE, and comparing it to
        # the existing calculation, that uses abcorr=LT. This is brute force, but it works. For MU69 approach,
        # it is 0.75 LORRI 4x4 pixels (ie, 3 1X1 pixels). This is bafflingly huge (I mean, we are headed
        # straight toward MU69, and it takes a month to move a pixel, and RTLT is only a few minutes). But I have
        # confirmed the math and the magnitude, and it works.

        (st_sc_target_j2k_nolt, _) = sp.spkezr(name_target, et, 'J2000',
                                               'NONE', name_observer)
        vec_sc_target_j2k_nolt = st_sc_target_j2k_nolt[0:3]
        (_, ra_sc_target_nolt,
         dec_sc_target_nolt) = sp.recrad(vec_sc_target_j2k_nolt)

        (x0, y0) = w.wcs_world2pix(ra_sc_target_nolt * hbt.r2d,
                                   dec_sc_target_nolt * hbt.r2d, 1)
        (x1, y1) = w.wcs_world2pix(ra_sc_target * hbt.r2d,
                                   dec_sc_target * hbt.r2d, 1)
        dx = x1 - x0
        dy = y1 - y0

        print(f'Compute backplanes: INRYPL pixel shift = {dx}, {dy}')

        dx_int = int(round(dx))
        dy_int = int(round(dy))

        do_roll = True

        if do_roll:
            print(
                f'compute_backplanes: Rolling by {dx_int}, {dy_int} due to INRYPL'
            )

            # Now shift all of the planes that need fixing. The dRA_km and dDec_km are calculated before INRYPL()
            # is applied, so they do not need to be shifted. I have validated that by plotting them.
            #
            # XXX NP.ROLL() is really not ideal. I should use a function that introduces NaN at the edge, not roll it.

            radius_arr = np.roll(np.roll(radius_arr, dy_int, axis=0),
                                 dx_int,
                                 axis=1)
            lon_arr = np.roll(np.roll(lon_arr, dy_int, axis=0), dx_int, axis=1)
            phase_arr = np.roll(np.roll(phase_arr, dy_int, axis=0),
                                dx_int,
                                axis=1)
            altitude_arr = np.roll(np.roll(altitude_arr, dy_int, axis=0),
                                   dx_int,
                                   axis=1)
        else:
            print(
                f'compute_backplanes: Skipping roll due to INRYPL, based on do_roll={do_roll}'
            )

        # Assemble the results into a backplane

        backplane = {
            'RA': ra_arr.astype(float),  # return radians
            'Dec': dec_arr.astype(float),  # return radians 
            'dRA_km': dra_arr.astype(float),
            'dDec_km': ddec_arr.astype(float),
            'Radius_eq': radius_arr.astype(float),
            'Longitude_eq': lon_arr.astype(float),
            'Phase': phase_arr.astype(float),
            'Altitude_eq': altitude_arr.astype(float),
            #             'x'            : x_arr.astype(float),
            #             'y'            : y_arr.astype(float),
            #             'z'            : z_arr.astype(float),
            #
        }

        # Assemble a bunch of descriptors, to be put into the FITS headers

        desc = {
            'RA of pixel, radians',
            'Dec of pixel, radians',
            'Offset from target in target plane, RA direction, km',
            'Offset from target in target plane, Dec direction, km',
            'Projected equatorial radius, km',
            'Projected equatorial longitude, km',
            'Sun-target-observer phase angle, radians',
            'Altitude above midplane, km',
            #                'X position of sky plane intercept',
            #                'Y position of sky plane intercept',
            #                'Z position of sky plane intercept'
        }

        # In the case of Jupiter, add a few extra fields

        if (name_body.upper() == 'JUPITER'):
            backplane['Ang_Thebe'] = ang_thebe_arr.astype(
                float)  # Angle to Thebe, in radians
            backplane['Ang_Metis'] = ang_metis_arr.astype(float)
            backplane['Ang_Amalthea'] = ang_amalthea_arr.astype(float)
            backplane['Ang_Adrastea'] = ang_adrastea_arr.astype(float)

            # If distance to any of the small sats is < 0.3 deg, then delete that entry in the dictionary

            if (np.amin(ang_thebe_arr) > fov_lorri):
                del backplane['Ang_Thebe']
            else:
                print("Keeping Thebe".format(np.min(ang_thebe_arr) * hbt.r2d))

            if (np.amin(ang_metis_arr) > fov_lorri):
                del backplane['Ang_Metis']
            else:
                print("Keeping Metis, min = {} deg".format(
                    np.min(ang_metis_arr) * hbt.r2d))

            if (np.amin(ang_amalthea_arr) > fov_lorri):
                del backplane['Ang_Amalthea']
            else:
                print("Keeping Amalthea, min = {} deg".format(
                    np.amin(ang_amalthea_arr) * hbt.r2d))

            if (np.amin(ang_adrastea_arr) > fov_lorri):
                del backplane['Ang_Adrastea']
            else:
                print("Keeping Adrastea".format(
                    np.min(ang_adrastea_arr) * hbt.r2d))

    # And return the backplane set

    return (backplane, desc)
コード例 #18
0
def get_fits_info_from_files_lorri(path,
                            file_tm = "/Users/throop/gv/dev/gv_kernels_new_horizons.txt", pattern=''):
    "Populate an astropy table with info from the headers of a list of LORRI files."
    import numpy as np
    import spiceypy as sp
    import glob
    import astropy
    from astropy.io import fits
    from astropy.table import Table
    import astropy.table
    import math
    import hbt
    

# For testing:
# file = '/Users/throop/Data/NH_Jring/data/jupiter/level2/lor/all/lor_0035020322_0x630_sci_1.fit' # 119 deg phase as per gv
# file = '/Users/throop/Data/NH_Jring/data/jupiter/level2/lor/all/lor_0034599122_0x630_sci_1.fit' # 7 deg phase, inbound

# t = hbt.get_fits_info_from_files_lorri(file)

# Flags: Do we do all of the files? Or just a truncated subset of them, for testing purposes?
    
    DO_TRUNCATED = False
    NUM_TRUNC = 100

# We should work to standardize this, perhaps allowing different versions of this function 
# for different instruments.

    d2r = np.pi /180.
    r2d = 1. / d2r

    sp.furnsh(file_tm)

# *** If path ends with .fit or .fits, then it is a file not a path. Don't expand it, but read it as a single file.

    if (('.fits' in path) or ('.fit' in path)):
        file_list = path
        files = [file_list]

    else:
        
        dir_data = path          
    #dir_data = '/Users/throop/data/NH_Jring/data/jupiter/level2/lor/all'
    # Start up SPICE
    
    
    # Get the full list of files
    # List only the files that match an (optional) user-supplied pattern, such as '_opnav'
    
        file_list = glob.glob(dir_data + '/*' + pattern + '.fit')
        files = np.array(file_list)
        indices = np.argsort(file_list)
        files = files[indices]

# Read the JD from each file. Then sort the files based on JD.

    jd = []
    for file in files:
        hdulist = fits.open(file)
        jd.append(hdulist[0].header['MET'])
        hdulist.close()
         
    fits_met     = [] # new list (same as array) 
    fits_startmet= [] 
    fits_stopmet = []
    fits_exptime = [] # starting time of exposure
    fits_target  = [] 
    fits_reqdesc = []     
    fits_reqcomm = [] # New 9-Oct-2018
    fits_reqid   = [] # New 9-Oct-2018
    fits_spcinst0= [] 
    fits_spcutcjd= []   
    fits_naxis1=   [] 
    fits_naxis2 =  []
    fits_sformat = [] # Data format -- '1x1' or '4x4'
    fits_spctscx = [] # sc - target, dx 
    fits_spctscy = [] # dy
    fits_spctscz = [] # dz
    fits_spctcb  = [] # target name
    fits_spctnaz = [] # Pole angle between target and instrument (i.e., boresight rotation angle)
    fits_rsolar  = [] # (DN/s)/(erg/cm^2/s/Ang/sr), Solar spectrum. Use for resolved sources.
    
    if (DO_TRUNCATED):
        files = files[0:NUM_TRUNC]
        
#files_short = np.array(files)
#for i in range(files.size):
#    files_short = files[i].split('/')[-1]  # Get just the filename itself

# Set up one iteration variable so we don't need to create it over and over
    num_obs = np.size(files)
    i_obs = np.arange(num_obs)
    
    print("Read " + repr(np.size(files)) + " files.")
    
    for file in files:
        print("Reading file " + file)
    
        hdulist = fits.open(file)
        header = hdulist[0].header
        
        keys = header.keys()
    
        fits_met.append(header['MET'])
        fits_exptime.append(header['EXPTIME'])
        fits_startmet.append(header['STARTMET'])
        fits_stopmet.append(header['STOPMET'])
        fits_target.append(header['TARGET'])
        fits_reqdesc.append(header['REQDESC'])
        fits_reqcomm.append(header['REQCOMM'])
        fits_reqid.append(header['REQID'])
        fits_spcinst0.append(header['SPCINST0'])
        fits_spcutcjd.append( (header['SPCUTCJD'])[3:]) # Remove the 'JD ' from before number
        fits_naxis1.append(header['NAXIS1'])
        fits_naxis2.append(header['NAXIS2'])
        fits_spctscx.append(header['SPCTSCX'])
        fits_spctscy.append(header['SPCTSCY'])
        fits_spctscz.append(header['SPCTSCZ'])    
        fits_spctnaz.append(header['SPCTNAZ'])    
        fits_sformat.append(header['SFORMAT'])
        fits_rsolar.append(header['RSOLAR'])   # NB: This will be in the level-2 FITS, but not level 1
                                             
        hdulist.close() # Close the FITS file

#print object
#print "done"

# Calculate distance to Jupiter in each of these
# Calc phase angle (to Jupiter)
# Eventually build backplanes: phase, RA/Dec, etc.
# Eventually Superimpose a ring on top of these
#  ** Not too hard. I already have a routine to create RA/Dec of ring borders.
# Eventually overlay stars 
#   Q: Will there be enough there?
# Eventually repoint based on stars
#  ** Before I allow repointing, I should search a star catalog and plot them.

# Convert some things to numpy arrays. Is there any disadvantage to this?

    met        = np.array(fits_met)
    jd         = np.array(fits_spcutcjd, dtype='d') # 'f' was rounding to one decimal place...
    naxis1     = np.array(fits_naxis1)
    naxis2     = np.array(fits_naxis2)
    target     = np.array(fits_target) # np.array can use string arrays as easily as float arrays
    instrument = np.array(fits_spcinst0)
    dx_targ    = np.array(fits_spctscx)
    dy_targ    = np.array(fits_spctscy)
    dz_targ    = np.array(fits_spctscz)
    desc       = np.array(fits_reqdesc)
    reqid      = np.array(fits_reqid)
    reqcomm    = np.array(fits_reqcomm)
    met0       = np.array(fits_startmet)
    met1       = np.array(fits_stopmet)
    exptime    = np.array(fits_exptime)
    rotation   = np.array(fits_spctnaz)
    sformat    = np.array(fits_sformat)
    rotation   = np.rint(rotation).astype(int)  # Turn rotation into integer. I only want this to be 0, 90, 180, 270... 
    rsolar     = np.array(fits_rsolar)
    
    files_short = np.zeros(num_obs, dtype = 'U60')

# Now do some geometric calculations and create new values for a few fields

    dist_targ = np.sqrt(dx_targ**2 + dy_targ**2 + dz_targ**2)

    phase = np.zeros(num_obs)
    utc = np.zeros(num_obs, dtype = 'U30')
    et = np.zeros(num_obs)
    subsclat = np.zeros(num_obs) # Sub-sc latitude
    subsclon = np.zeros(num_obs) # Sub-sc longitude
    
    name_observer = 'New Horizons'
    frame = 'J2000'
    abcorr = 'LT+S'
#         Note that using light time corrections alone ("LT") is 
#         generally not a good way to obtain an approximation to an 
#         apparent target vector:  since light time and stellar 
#         aberration corrections often partially cancel each other, 
#         it may be more accurate to use no correction at all than to 
#         use light time alone. 

# Fix the MET. The 'MET' field in fits header is actually not the midtime, but the time of the first packet.
# I am going to replace it with the midtime.
# *** No, don't do that. The actual MET field is used for timestamping -- keep it as integer.

#    met = (met0 + met1) / 2.

# Loop over all images

    for i in i_obs:
    
# Get the ET and UTC, from the JD. These are all times *on s/c*, which is what we want
    
      et[i] = sp.utc2et('JD ' + repr(jd[i]))
      utc[i] = sp.et2utc(et[i], 'C', 2)
    
# Calculate Sun-Jupiter-NH phase angle for each image 
    
      (st_jup_sc, ltime) = sp.spkezr('Jupiter', et[i], frame, abcorr, 'New Horizons') #obs, targ
      (st_sun_jup, ltime) = sp.spkezr('Sun', et[i], frame, abcorr, 'Jupiter')
      ang_scat = sp.vsep(st_sun_jup[0:3], st_jup_sc[0:3])
      phase[i] = math.pi - ang_scat
#      phase[i] = ang_scat
      files_short[i] = files[i].split('/')[-1]
# Calc sub-sc lon/lat
      
      mx = sp.pxform(frame,'IAU_JUPITER', et[i])
      st_jup_sc_iau_jup = sp.mxv(mx, st_jup_sc[0:3])
      
      (radius,subsclon[i],subsclat[i]) = sp.reclat(st_jup_sc[0:3])  # Radians
      (radius,subsclon[i],subsclat[i]) = sp.reclat(st_jup_sc_iau_jup)  # Radians

# Stuff all of these into a Table

    t = Table([i_obs, met, utc, et, jd, files, files_short, naxis1, naxis2, target, instrument, 
               dx_targ, dy_targ, dz_targ, reqid, 
               met0, met1, exptime, phase, subsclat, subsclon, naxis1, 
               naxis2, rotation, sformat, rsolar, desc, reqcomm], 
               
               names = ('#', 'MET', 'UTC', 'ET', 'JD', 'Filename', 'Shortname', 'N1', 'N2', 'Target', 'Inst', 
                        'dx', 'dy', 'dz', 'ReqID',
                        'MET Start', 'MET End', 'Exptime', 'Phase', 'Sub-SC Lat', 'Sub-SC Lon', 'dx_pix', 
                        'dy_pix', 'Rotation', 'Format', 'RSolar', 'Desc', 'Comment'))
    
# Define units for a few of the columns
                        
    t['Exptime'].unit = 's'
    t['Sub-SC Lat'].unit = 'degrees'

# Create a dxyz_targ column, from dx dy dz. Easy!

    t['dxyz'] = np.sqrt(t['dx']**2 + t['dy']**2 + t['dz']**2)  # Distance, in km

    return t
コード例 #19
0
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")
コード例 #20
0
        # Get the distance from Jupiter center to image center
        
        vec_nh_center = sp.radrec(1, radec_center[0][0], radec_center[0][1])  # Vector from NH, to center of LORRI frame
        ang_jup_center = sp.vsep(vec_nh_jup, vec_nh_center)                   # Ang Sep btwn Jup and LORRI, radians
        dist_jup_center_rj = ang_jup_center * sp.vnorm(vec_nh_jup) / rj_km    # Convert from radians into RJ
        width_lorri = 0.3*hbt.d2r                                             # LORRI full width, radians
    
        dist_jup_center_rj_range = np.array([ang_jup_center-width_lorri/2, ang_jup_center+width_lorri/2]) * \
            sp.vnorm(vec_nh_jup) / rj_km                                      # Finally, we have min and max dist
                                                                              # in the central row of array, in rj
        range_rj_arr.append(sp.vnorm(vec_nh_jup)/rj_km)
   
        # Calc the elevation angle (aka sub-obs lat on Jup)
        
        mx = sp.pxform('J2000', 'IAU_JUPITER', t_i['ET'])
        vec_nh_jup_jup = sp.mxv(mx, vec_nh_jup)
        (dist, lon, lat) = sp.recrad(-vec_nh_jup_jup)
        
        angle_elev_arr.append(lat * hbt.r2d)  # Save the sub-obs latitude, in degrees
        
        # If we are on the LHS limb, then we need to reverse this, and negate it.
    
        if (is_limb_left):
            dist_jup_center_rj_range = -1 * dist_jup_center_rj_range[::-1]

        dist_proj_rj_arr.append((dist_jup_center_rj_range))
        
        # Get the distance above the ring plane -- that is, distance from image center to ring plane, projected.
        # To do this:
        #   - Project a vector from s/c along central LORRI pixel.
        #   - Make a SPICE 'line' from s/c along this vector
コード例 #21
0
def cassini_titan_test(flyby, anodes=False):
    times = []
    states = []
    lons, lats, alts = [], [], []
    crossvecs_lonlatalts = []
    crossvecs_lonlatalts_spicenormal = []
    cmats = []
    vecs = []
    anode_vecs = []
    anode_seps = [[], [], [], [], [], [], [], []]
    anodes1, anodes8 = [], []
    crossvecs = []
    angularseparations = []
    beamanodes = []
    spiceplanenormals = []

    windsdf = pd.read_csv("crosswinds_full.csv", index_col=0, parse_dates=True)
    tempdf = windsdf[windsdf['Flyby'] == flyby]
    for tempdatetime, negwindspeed, poswindspeed in zip(
            pd.to_datetime(tempdf['Bulk Time']),
            tempdf["Negative crosstrack velocity"],
            tempdf["Positive crosstrack velocity"]):
        print("---------")
        print(tempdatetime)
        times.append(tempdatetime)
        beamanodes.append(np.mean(ELS_ramanodes(tempdatetime)) + 1)
        states.append(cassini_phase(
            tempdatetime.strftime('%Y-%m-%dT%H:%M:%S')))
        # print(states[-1])
        lon, lat, alt = spice.recpgr("TITAN", states[-1][:3],
                                     spice.bodvrd("TITAN", 'RADII', 3)[1][0],
                                     1.44e-4)
        lons.append(lon * spice.dpr())
        lats.append(lat * spice.dpr())
        alts.append(alt)
        # vecs.append(cassini_act_2_titan(tempdatetime))
        crossvec = caps_crosstrack(tempdatetime,
                                   np.mean([negwindspeed, poswindspeed]))
        print("crossvec", crossvec)
        testspicenormal, anode1, anode8 = caps_crosstrack_spice(
            tempdatetime, np.mean([negwindspeed, poswindspeed]))
        anodes1.append(anode1)
        anodes8.append(anode8)
        spiceplanenormals.append(testspicenormal)
        print("test spice normal", testspicenormal)
        jacobian = spice.dpgrdr("TITAN", states[-1][0], states[-1][1],
                                states[-1][2],
                                spice.bodvrd('TITAN', 'RADII',
                                             3)[1][0], 1.44e-4)
        # print("jacobian", jacobian)
        crossvec_lonlatalt = spice.mxv(jacobian, spice.vhat(crossvec))
        crossvec_lonlatalt_spicenormal = spice.mxv(jacobian, testspicenormal)

        # print("recpgr", lon, lat, alt)
        # print("crossvec latlon", crossvec_lonlatalt)
        # print("crossvec latlon vhat", spice.vhat(crossvec_latlon))
        crossvecs.append(crossvec)
        crossvecs_lonlatalts.append(crossvec_lonlatalt)
        crossvecs_lonlatalts_spicenormal.append(crossvec_lonlatalt_spicenormal)
        # print("Time", tempdatetime)
        # print("position", states[-1][:3])
        # print("velocity", spice.vhat(states[-1][3:]))
        # print("direction", spice.vhat(vecs[-1]))

        # if anodes:
        #     anode_vecs.append(caps_all_anodes(tempdatetime))
        #     print("anode vecs 1 & 8", anode_vecs[-1][0], anode_vecs[-1][7])
        #     # spiceplanenormal = spice.psv2pl(states[-1][:3],anode_vecs[-1][0],anode_vecs[-1][7])
        #     # print("SPICE NORMAL", spice.pl2nvp(spiceplanenormal))
        #     #
        #     # spiceplanenormals.append(-1*spice.pl2nvp(spiceplanenormal)[0])
        #     # print("Crossvec", crossvec)
        #     for anodecounter, i in enumerate(anode_vecs[-1]):
        #         # print(anodecounter,anode_vecs[-1][anodecounter])
        #         anode_seps[anodecounter].append(
        #             spice.vsep(spice.vhat(states[-1][3:]), spice.vhat(anode_vecs[-1][anodecounter])) * spice.dpr())
        # print("anodeseps",anode_seps)
        # print("Angular Separation", spice.vsep(spice.vhat(states[-1][3:]), spice.vhat(vecs[-1])) * spice.dpr())

    x, y, z, u, v, w = [], [], [], [], [], []

    for i in states:
        x.append(i[0])
        y.append(i[1])
        z.append(i[2])

    # CAPS direction
    for i in vecs:
        u.append(i[0])
        v.append(i[1])
        w.append(i[2])

    # Crosstrack
    u2, v2, w2 = [], [], []
    for j in crossvecs:
        u2.append(j[0])
        v2.append(j[1])
        w2.append(j[2])

    # SPICE plane normal
    u3, v3, w3 = [], [], []
    for j in spiceplanenormals:
        u3.append(j[0])
        v3.append(j[1])
        w3.append(j[2])

    # Ram Direction
    u1, v1, w1 = [], [], []
    for i in states:
        u1.append(i[3])
        v1.append(i[4])
        w1.append(i[5])

    fig = plt.figure()

    u = np.linspace(0, 2 * np.pi, 50)
    v = np.linspace(0, np.pi, 50)
    x_sphere = 2574.7 * np.outer(np.cos(u), np.sin(v))
    y_sphere = 2574.7 * np.outer(np.sin(u), np.sin(v))
    z_sphere = 2574.7 * np.outer(np.ones(np.size(u)), np.cos(v))

    ax = fig.add_subplot(111, projection='3d')
    # Plot the surface
    # ax.plot_wireframe(x_sphere, y_sphere, z_sphere, color='b')
    # ax.plot(x, y, z, alpha=0.5, color='k')
    if anodes:
        for timecounter, (i, j) in enumerate(zip(anodes1, anodes8)):
            X = x[timecounter]
            Y = y[timecounter]
            Z = z[timecounter]
            # print(i)
            # for anodecounter, j in enumerate(i):
            #     if anodecounter in [0, 7]:
            #         ax.quiver(X, Y, Z, j[0], j[1], j[2], length=20, color='C' + str(anodecounter))
            # print(timecounter, i, j)
            ax.quiver(X, Y, Z, i[0], i[1], i[2], length=30, color='C1')
            ax.quiver(X, Y, Z, j[0], j[1], j[2], length=30, color='C2')

    ax.quiver(x, y, z, u2, v2, w2, length=30, color='m')
    ax.quiver(x, y, z, u1, v1, w1, length=5, color='k')
    ax.quiver(x, y, z, u3, v3, w3, length=30, color='r')
    ax.set_xlabel("X")
    ax.set_ylabel("Y")
    ax.set_zlabel("Z")
    ax.set_xlim(min(x), max(x))
    ax.set_ylim(min(y), max(y))
    ax.set_zlim(min(z), max(z))

    dlat, dlon = [], []
    for i in crossvecs_lonlatalts:
        dlat.append(i[1])
        dlon.append(i[0])

    dlat_spicenormal, dlon_spicenormal = [], []
    for i in crossvecs_lonlatalts_spicenormal:
        dlat_spicenormal.append(i[1])
        dlon_spicenormal.append(i[0])

    fig2, ax2 = plt.subplots()
    ax2.plot(lons, lats)
    ax2.quiver(lons, lats, dlon, dlat)
    ax2.quiver(lons, lats, dlon_spicenormal, dlat_spicenormal, color='r')
    ax2.set_xlabel("Longitude")
    ax2.set_ylabel("Latitude")
    ax2.grid()
コード例 #22
0
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
コード例 #23
0
    def __Geometry(self, boresight=''):

        #if self.geometry_flag is True and \
        #                self.time.window.all() == self.previous_tw.all():
        #    return

        distance = []
        altitude = []
        boresight_latitude = []
        boresight_longitude = []
        latitude = []
        longitude = []
        subpoint_xyz = []
        subpoint_pgc = []
        subpoint_pcc = []
        zaxis_target_angle = []
        myaxis_target_angle = []
        yaxis_target_angle = []
        xaxis_target_angle = []
        beta_angle = []

        qs, qx, qy, qz = [], [], [] ,[]
        x, y, z = [],[],[]


        tar = self.target
        time = self.time

        for et in time.window:

            try:
                #
                # Compute the distance
                #
                ptarg, lt = spiceypy.spkpos(tar.name, et, tar.frame, time.abcorr,
                                          self.name)
                x.append(ptarg[0])
                y.append(ptarg[1])
                z.append(ptarg[2])

                vout, vmag = spiceypy.unorm(ptarg)
                distance.append(vmag)


                #
                # Compute the geometric sub-observer point.
                #
                if tar.frame == 'MARSIAU':
                    tar_frame = 'IAU_MARS'
                else:
                    tar_frame = tar.frame
                spoint, trgepc, srfvec = spiceypy.subpnt(tar.method, tar.name, et,
                                                       tar_frame, time.abcorr,
                                                       self.name)
                subpoint_xyz.append(spoint)

                #
                # Compute the observer's altitude from SPOINT.
                #
                dist = spiceypy.vnorm(srfvec)
                altitude.append(dist)


                #
                # Convert the sub-observer point's rectangular coordinates to
                # planetographic longitude, latitude and altitude.
                #
                spglon, spglat, spgalt = spiceypy.recpgr(tar.name, spoint,
                                                       tar.radii_equ, tar.flat)

                #
                # Convert radians to degrees.
                #
                spglon *= spiceypy.dpr()
                spglat *= spiceypy.dpr()

                subpoint_pgc.append([spglon, spglat, spgalt])

                #
                #  Convert sub-observer point's rectangular coordinates to
                #  planetocentric radius, longitude, and latitude.
                #
                spcrad, spclon, spclat = spiceypy.reclat(spoint)


                #
                # Convert radians to degrees.
                #
                spclon *= spiceypy.dpr()
                spclat *= spiceypy.dpr()

                subpoint_pcc.append([spclon, spclat, spcrad])
                latitude.append(spclat) #TODO: Remove with list extraction
                longitude.append(spclon)  # TODO: Remove with list extraction

                #
                # Compute the geometric sub-boresight point.
                #
                if tar.frame == 'MARSIAU':
                    tar_frame = 'IAU_MARS'
                else:
                    tar_frame = tar.frame


                if boresight:
                    try:
                        id = spiceypy.bodn2c(boresight)
                        (shape,framen, bsight, n, bounds) = spiceypy.getfov(id, 80)
                        mat = spiceypy.pxform(framen,tar_frame,et)
                    except:
                        framen = boresight
                        bsight = 0,0,1
                else:
                    bsight = self.name

                try:
                    if tar.method == 'INTERCEPT/ELLIPSOID':
                        method = 'ELLIPSOID'
                    else:
                        method = tar.method
                    spoint, trgepc, srfvec = spiceypy.sincpt(method, tar.name, et,
                                                           tar_frame, time.abcorr,
                                                           self.name, framen,
                                                           bsight)

                    #
                    # Convert the sub-observer point's rectangular coordinates to
                    # planetographic longitude, latitude and altitude.
                    #
                    spglon, spglat, spgalt = spiceypy.recpgr(tar.name, spoint,
                                                           tar.radii_equ, tar.flat)

                    #
                    # Convert radians to degrees.
                    #
                    spglon *= spiceypy.dpr()
                    spglat *= spiceypy.dpr()


                    #
                    #  Convert sub-observer point's rectangular coordinates to
                    #  planetocentric radius, longitude, and latitude.
                    #
                    spcrad, spclon, spclat = spiceypy.reclat(spoint)


                    #
                    # Convert radians to degrees.
                    #
                    spclon *= spiceypy.dpr()
                    spclat *= spiceypy.dpr()

                    boresight_latitude.append(spclat)
                    boresight_longitude.append(spclon)

                except:
                    pass

                #
                # Compute the angle between the observer's S/C axis and the
                # geometric sub-observer point
                #
                obs_tar, ltime = spiceypy.spkpos(tar.name, et,
                                                       'J2000', time.abcorr,
                                                       self.name)
                obs_zaxis  = [0,  0, 1]
                obs_myaxis = [0, -1, 0]
                obs_yaxis = [0, 1, 0]
                obs_xaxis = [1, 0, 0]

                #
                # We need to account for when there is no CK attitude available.
                #
                try:
                    matrix = spiceypy.pxform(self.frame, 'J2000', et)

                    z_vecout = spiceypy.mxv(matrix, obs_zaxis)
                    zax_target_angle = spiceypy.vsep(z_vecout, obs_tar)
                    zax_target_angle *= spiceypy.dpr()
                    zaxis_target_angle.append(zax_target_angle)

                    my_vecout = spiceypy.mxv(matrix, obs_myaxis)
                    myax_target_angle = spiceypy.vsep(my_vecout, obs_tar)
                    myax_target_angle *= spiceypy.dpr()
                    myaxis_target_angle.append(myax_target_angle)

                    y_vecout = spiceypy.mxv(matrix, obs_myaxis)
                    yax_target_angle = spiceypy.vsep(y_vecout, obs_tar)
                    yax_target_angle *= spiceypy.dpr()
                    yaxis_target_angle.append(yax_target_angle)

                    x_vecout = spiceypy.mxv(matrix, obs_myaxis)
                    xax_target_angle = spiceypy.vsep(x_vecout, obs_tar)
                    xax_target_angle *= spiceypy.dpr()
                    xaxis_target_angle.append(xax_target_angle)


                    quat = spiceypy.m2q(spiceypy.invert(matrix))
                    qs.append(quat[0])
                    qx.append(-1*quat[1])
                    qy.append(-1*quat[2])
                    qz.append(-1*quat[3])

                except:
                    zaxis_target_angle.append(0.0)
                    myaxis_target_angle.append(0.0)
                    yaxis_target_angle.append(0.0)
                    xaxis_target_angle.append(0.0)
                    qs.append(0.0)
                    qx.append(0.0)
                    qy.append(0.0)
                    qz.append(0.0)

                beta_angle.append(spiops.beta_angle(self.name, self.target.name,
                                                    et))
            except:
                boresight_latitude = 0
                boresight_longitude = 0
                distance = 0
                altitude = 0
                latitude = 0
                longitude = 0
                subpoint_xyz = [0,0,0]
                subpoint_pgc =  [0,0,0]
                subpoint_pcc =  [0,0,0]
                zaxis_target_angle = 0
                myaxis_target_angle = 0
                yaxis_target_angle = 0
                xaxis_target_angle = 0
                beta_angle = 0
                (qx, qy, qz, qs) = 0, 0, 0, 0
                (x, y, z) = 0, 0, 0

        self.boresight_latitude = boresight_latitude
        self.boresight_longitude = boresight_longitude
        self.distance = distance
        self.altitude = altitude
        self.latitude = latitude
        self.longitude = longitude
        self.subpoint_xyz = subpoint_xyz
        self.subpoint_pgc = subpoint_pgc
        self.subpoint_pcc = subpoint_pcc
        self.zaxis_target_angle = zaxis_target_angle
        self.myaxis_target_angle = myaxis_target_angle
        self.yaxis_target_angle = yaxis_target_angle
        self.xaxis_target_angle = xaxis_target_angle
        self.beta_angle = beta_angle
        self.quaternions = [qx, qy, qz, qs]
        self.trajectory = [x,y,z]

        self.geometry_flag = True
        self.previous_tw = self.time.window

        return
コード例 #24
0
def test_frame_mu69_sunflower():
    
    """
    This is just a quick test routine to check that MU69 Sunflower frame is more-or-less working.
    
    This program prints the RA/Dec of MU69's Y and Z axes, under a variety of different conditions.
    
    No options.
    
    Things to verify here:
        - That the Z RA/Dec point toward the orbit pole RA/Dec in all cases. 
              Small deviations << 1 deg allowed
              
        - That the Y RA/Dec point toward the Sun always for _ROT Frames.
              (ie, Y RA/Dec changes with time.)
              
        - That the Y RA/Dec point toward the Sun for the _INERT frames on 1 Jan 2015,
              and slowly move after / before that, at roughly 1 deg/year.
              (ie, Y RA/Dec is pretty much fixed)
              
    16-Jan-2018. HBT verified that output values look pretty good.

    """
    
    tms = ['kernels_sunflower.tm']  # Define the metakernel, which in turn calls the sunflower .tf frame

    frames = ['2014_MU69_SUNFLOWER_ROT', '2014_MU69_SUNFLOWER_INERT']
        
    utcs = ['1 Jan 2005 00:00:00', '1 Jan 2015 00:00:00']
    
    frame_j2k = 'J2000'
    
    # Get values from Simon Porter. See email from MRS ~16-Jan-2018.
    # These RA/Dec values are also put into the .tf file which I have made.
    
    ra_mu69_sp = 272.426110231801*hbt.d2r
    dec_mu69_sp = 68.831520928192*hbt.d2r
    
    # Define the MU69 Z axis. We will rotate this, and it should point in specified direction.
    
    z_mu69 = [0, 0, 1] # MU69 +Z axis. It should point to the specified RA/Dec
    y_mu69 = [0, 1, 0] # MU69 +Y axis. It should point away from the Sun
    
    print("Simon Porter pole position:")
    print("RA = {}, Dec = {}".format(ra_mu69_sp*hbt.r2d, dec_mu69_sp*hbt.r2d))
    print("---")
    
    # Loop over input parameters. For each combination, do a calculation, and print the output.
    
    for tm in tms:    
        for frame in frames: 
            for utc in utcs:
                sp.furnsh(tm)
                et = sp.utc2et(utc)
                mx = sp.pxform(frame, frame_j2k, et)
            
                z_mu69_j2k = sp.mxv(mx, z_mu69)                
                (_, ra_z, dec_z) = sp.recrad(z_mu69_j2k)

                y_mu69_j2k = sp.mxv(mx, y_mu69)                
                (_, ra_y, dec_y) = sp.recrad(y_mu69_j2k)
                
                print("Metakernel: {}".format(tm))
                print("UTC:        {}".format(utc))
                print("Frame:      {}".format(frame))
                print("Matrix:     \n{}".format(mx))
                print("Y RA = {}, Dec = {}".format(ra_y*hbt.r2d, dec_y*hbt.r2d))
                print("Z RA = {}, Dec = {}".format(ra_z*hbt.r2d, dec_z*hbt.r2d))
                print("\n---\n")
コード例 #25
0
def compute_backplanes(file, name_target, frame, name_observer, angle1=0, angle2=0, angle3=0):
    
    """
    Returns a set of backplanes for a single specified image. The image must have WCS coords available in its header.
    Backplanes include navigation info for every pixel, including RA, Dec, Eq Lon, Phase, etc.
    
    The results are returned to memory, and not written to a file.
    
    SPICE kernels must be alreaded loaded, and spiceypy running.
    
    Parameters
    ----
    
    file:
        String. Input filename, for FITS file.
    frame:
        String. Reference frame of the target body. 'IAU_JUPITER', 'IAU_MU69', '2014_MU69_SUNFLOWER_ROT', etc.
        This is the frame that the Radius_eq and Longitude_eq are computed in.
    name_target:
        String. Name of the central body. All geometry is referenced relative to this (e.g., radius, azimuth, etc)
    name_observer:
        String. Name of the observer. Must be a SPICE body name (e.g., 'New Horizons')
    
    Optional Parameters
    ----
    
    angle{1,2,3}:
        **NOT REALLY TESTED. THE BETTER WAY TO CHANGE THE ROTATION IS TO USE A DIFFERENT FRAME.**

        Rotation angles which are applied when defining the plane in space that the backplane will be generated for.
        These are applied in the order 1, 2, 3. Angles are in radians. Nominal values are 0.
       
        This allows the simulation of (e.g.) a ring system inclined relative to the nominal body equatorial plane.
        
        For MU69 sunflower rings, the following descriptions are roughly accurate, becuase the +Y axis points
        sunward, which is *almost* toward the observer. But it is better to experiment and find the 
        appropriate angle that way, than rely on this ad hoc description. These are close for starting with.
 
                      - `angle1` = Tilt front-back, from face-on. Or rotation angle, if tilted right-left.
                      - `angle2` = Rotation angle, if tilted front-back. 
                      - `angle3` = Tilt right-left, from face-on.
    
    do_fast:
        Boolean. If set, generate only an abbreviated set of backplanes. **NOT CURRENTLY IMPLEMENTED.**
        
    Output
    ----

    Output is a tuple, consisting of each of the backplanes, and a text description for each one. 
    The size of each of these arrays is the same as the input image.
    
    The position of each of these is the plane defined by the target body, and the normal vector to the observer.
    
    output = (backplanes, descs)
    
        backplanes = (ra,      dec,      radius_eq,      longitude_eq,      phase)
        descs      = (desc_ra, desc_dec, desc_radius_eq, desc_longitude_eq, desc_phase)
    
    Radius_eq:
        Radius, in the ring's equatorial plane, in km
    Longitude_eq:
        Longitude, in the equatorial plane, in radians (0 .. 2pi)
    RA:
        RA of pixel, in radians
    Dec:
        Dec of pixel, in
    dRA:
        Projected offset in RA  direction between center of body (or barycenter) and pixel, in km.
    dDec:
        Projected offset in Dec direction between center of body (or barycenter) and pixel, in km.
        
    Z:  
        Vertical value of the ring system.
        
    With special options selected (TBD), then additional backplanes will be generated -- e.g., a set of planes
    for each of the Jovian satellites in the image, or sunflower orbit, etc.
    
    No new FITS file is written. The only output is the returned tuple.
    
    """

    if not(frame):
        raise ValueError('frame undefined')
    
    if not(name_target):
        raise ValueError('name_target undefined')
        
    if not(name_observer):
        raise ValueError('name_observer undefined')
        
    name_body = name_target  # Sometimes we use one, sometimes the other. Both are identical
    
    fov_lorri = 0.3 * hbt.d2r
   
    abcorr = 'LT'

    do_satellites = False  # Flag: Do we create an additional backplane for each of Jupiter's small sats?
    
    # Open the FITS file
    
    w       = WCS(file) # Warning: I have gotten a segfault here before if passing a FITS file with no WCS info.    
    hdulist = fits.open(file)
    
    et      = float(hdulist[0].header['SPCSCET']) # ET of mid-exposure, on s/c
    n_dx    = int(hdulist[0].header['NAXIS1']) # Pixel dimensions of image. Both LORRI and MVIC have this.
    n_dy    = int(hdulist[0].header['NAXIS2'])
    
    hdulist.close()

    # Setup the output arrays
    
    lon_arr    = np.zeros((n_dy, n_dx))     # Longitude of pixel (defined with recpgr)
    lat_arr    = np.zeros((n_dy, n_dx))     # Latitude of pixel (which is zero, so meaningless)
    radius_arr = np.zeros((n_dy, n_dx))     # Radius, in km
    altitude_arr= np.zeros((n_dy, n_dx))    # Altitude above midplane, in km
    ra_arr     = np.zeros((n_dy, n_dx))     # RA of pixel
    dec_arr    = np.zeros((n_dy, n_dx))     # Dec of pixel
    dra_arr    = np.zeros((n_dy, n_dx))     # dRA  of pixel: Distance in sky plane between pixel and body, in km. 
    ddec_arr   = np.zeros((n_dy, n_dx))     # dDec of pixel: Distance in sky plane between pixel and body, in km.
    phase_arr  = np.zeros((n_dy, n_dx))     # Phase angle    
    x_arr      = np.zeros((n_dy, n_dx))     # Intersection of sky plane: X pos in bdoy coords
    y_arr      = np.zeros((n_dy, n_dx))     # Intersection of sky plane: X pos in bdoy coords
    z_arr      = np.zeros((n_dy, n_dx))     # Intersection of sky plane: X pos in bdoy coords
    
# =============================================================================
#  Do the backplane, in the general case.
#  This is a long routine, because we deal with all the satellites, the J-ring, etc.        
# =============================================================================
    
    if (True):
        
        # Look up body parameters, used for PGRREC().
    
        (num, radii) = sp.bodvrd(name_target, 'RADII', 3)
        
        r_e = radii[0]
        r_p = radii[2]
        flat = (r_e - r_p) / r_e

        # Define a SPICE 'plane' along the plane of the ring.
        # Do this in coordinate frame of the body (IAU_JUPITER, 2014_MU69_SUNFLOWER_ROT, etc).
        
# =============================================================================
# Set up the Jupiter system specifics
# =============================================================================
            
        if (name_target.upper() == 'JUPITER'):
            plane_target_eq = sp.nvp2pl([0,0,1], [0,0,0])    # nvp2pl: Normal Vec + Point to Plane. Jupiter north pole?

            # For Jupiter only, define a few more output arrays for the final backplane set

            ang_metis_arr    = np.zeros((n_dy, n_dx))   # Angle from pixel to body, in radians
            ang_adrastea_arr = ang_metis_arr.copy()
            ang_thebe_arr    = ang_metis_arr.copy()
            ang_amalthea_arr = ang_metis_arr.copy()

# =============================================================================
# Set up the MU69 specifics
# =============================================================================
        
        if ('MU69' in name_target.upper()):
            
        # Define a plane, which is the plane of sunflower rings (ie, X-Z plane in Sunflower frame)
        # If additional angles are passed, then create an Euler matrix which will do additional angles of rotation.
        # This is defined in the 'MU69_SUNFLOWER' frame

            vec_plane = [0, 1, 0]                                 # Use +Y (anti-sun dir), which is normal to XZ.
#            vec_plane = [0, 0, 1]                                 # Use +Y (anti-sun dir), which is normal to XZ.
            plane_target_eq = sp.nvp2pl(vec_plane, [0,0,0]) # "Normal Vec + Point to Plane". 0,0,0 = origin.

           # XXX NB: This plane in in body coords, not J2K coords. This is what we want, because the 
           # target intercept calculation is also done in body coords.
        
# =============================================================================
# Set up the various output planes and arrays necessary for computation
# =============================================================================

        # Get xformation matrix from J2K to target system coords. I can use this for points *or* vectors.
                
        mx_j2k_frame = sp.pxform('J2000', frame, et) # from, to, et
        
        # Get vec from body to s/c, in both body frame, and J2K.
        # NB: The suffix _j2k indicates j2K frame. _frame indicates the frame of target (IAU_JUP, MU69_SUNFLOWER, etc)
               
        (st_target_sc_frame, lt) = sp.spkezr(name_observer, et, frame,   abcorr, name_target)
        (st_sc_target_frame, lt) = sp.spkezr(name_target,   et, frame,   abcorr, name_observer)
        (st_target_sc_j2k, lt)   = sp.spkezr(name_observer, et, 'J2000', abcorr, name_target)     
        (st_sc_target_j2k, lt)   = sp.spkezr(name_target,   et, 'J2000', abcorr, name_observer)
        
        vec_target_sc_frame = st_target_sc_frame[0:3]
        vec_sc_target_frame = st_sc_target_frame[0:3]
        vec_target_sc_j2k   = st_target_sc_j2k[0:3]
        vec_sc_target_j2k   = st_sc_target_j2k[0:3]
        
        dist_target_sc      = sp.vnorm(vec_target_sc_j2k)   # Get target distance, in km
        
        # vec_sc_target_frame = -vec_target_sc_frame # ACTUALLY THIS IS NOT TRUE!! ONLY TRUE IF ABCORR=NONE.
        
        # Name this vector a 'point'. INRYPL requires a point argument.
            
        pt_target_sc_frame = vec_target_sc_frame
    
        # Look up RA and Dec of target (from sc), in J2K 
        
        (_, ra_sc_target, dec_sc_target) = sp.recrad(vec_sc_target_j2k)

        # Get vector from target to sun. We use this later for phase angle.
        
        (st_target_sun_frame, lt) = sp.spkezr('Sun', et, frame, abcorr, name_target) # From body to Sun, in body frame
        vec_target_sun_frame = st_target_sun_frame[0:3]
        
        # Create a 2D array of RA and Dec points
        # These are made by WCS, so they are guaranteed to be right.
        
        xs = range(n_dx)
        ys = range(n_dy)
        (i_x_2d, i_y_2d) = np.meshgrid(xs, ys)
        (ra_arr, dec_arr) = w.wcs_pix2world(i_x_2d, i_y_2d, False) # Returns in degrees
        ra_arr  *= hbt.d2r                                        # Convert to radians
        dec_arr *= hbt.d2r
        
        # Compute the projected distance from MU69, in the sky plane, in km, for each pixel.
        # dist_target_sc is the distance to MU69, and we use this to convert from radians, to km.
        # 16-Oct-2018. I had been computing this erroneously. It should be *cosdec, not /cosdec.
        
        dra_arr  = (ra_arr   - ra_sc_target) * dist_target_sc * np.cos(dec_arr)
        ddec_arr = (dec_arr - dec_sc_target) * dist_target_sc  # Convert to km
    
# =============================================================================
#  Compute position for additional Jupiter bodies, as needed
# =============================================================================
    
        if (name_target.upper() == 'JUPITER'):
            vec_metis_j2k,lt     = sp.spkezr('Metis',    et, 'J2000', abcorr, 'New Horizons')
            vec_adrastea_j2k,lt  = sp.spkezr('Adrastea', et, 'J2000', abcorr, 'New Horizons')
            vec_thebe_j2k,lt     = sp.spkezr('Thebe',    et, 'J2000', abcorr, 'New Horizons')
            vec_amalthea_j2k,lt  = sp.spkezr('Amalthea', et, 'J2000', abcorr, 'New Horizons')
            
            vec_metis_j2k        = np.array(vec_metis_j2k[0:3])
            vec_thebe_j2k        = np.array(vec_thebe_j2k[0:3])
            vec_adrastea_j2k     = np.array(vec_adrastea_j2k[0:3])
            vec_amalthea_j2k     = np.array(vec_amalthea_j2k[0:3])
        
# =============================================================================
# Loop over pixels in the output image
# =============================================================================
        
        for i_x in xs:
            for i_y in ys:
        
                # Look up the vector direction of this single pixel, which is defined by an RA and Dec
                # Vector is thru pixel to ring, in J2K. 
                # RA and Dec grids are made by WCS, so they are guaranteed to be right.
        
                vec_pix_j2k =  sp.radrec(1., ra_arr[i_y, i_x], dec_arr[i_y, i_x]) 
                
                # Convert vector along the pixel direction, from J2K into the target body frame
          
                vec_pix_frame = sp.mxv(mx_j2k_frame, vec_pix_j2k)
        
                # And calculate the intercept point between this vector, and the ring plane.
                # All these are in body coordinates.
                # plane_target_eq is defined as the body's equatorial plane (its XZ for MU69).
                
                # ** Some question as to whether we should shoot this vector at the ring plane, or the sky plane.
                # Ring plane is normally the one we want. But, for the case of edge-on rings, the eq's break down.
                # So, we should use the sky plane instead. Testing shows that for the case of MU69 Eq's break down 
                # for edge-on rings... there is always an ambiguity.

                # ** For testing, try intersecting the sky plane instead of the ring plane.
                # ** Confirmed: Using sky plane gives identical results in case of face-on rings.
                #    And it gives meaningful results in case of edge-on rings, where ring plane did not.
                #    However, for normal rings (e.g., Jupiter), we should continue using the ring plane, not sky plane.
                
                do_sky_plane = True  # For ORT4, where we want to use euler angles, need to set this to False
                
                if do_sky_plane and ('MU69' in name_target):
                    plane_sky_frame = sp.nvp2pl(vec_sc_target_frame, [0,0,0])  # Frame normal to s/c vec, cntrd on MU69
                    (npts, pt_intersect_frame) = sp.inrypl(pt_target_sc_frame, vec_pix_frame, plane_sky_frame) 
                    
                    # pt_intersect_frame is the point where the ray hits the skyplane, in the coordinate frame
                    # of the target body.
    
                else:                         # Calc intersect into equator of target plane (ie, ring plane)
                    (npts, pt_intersect_frame) = sp.inrypl(pt_target_sc_frame, vec_pix_frame, plane_target_eq) 
                                                                                             # pt, vec, plane
                    
                # Swap axes in target frame if needed.                
                # In the case of MU69 (both sunflower and tunacan), the frame is defined s.t. the ring 
                # is in the XZ plane, not XY. This is strange (but correct).
                # I bet MU69 is the only ring like this. Swap it so that Z means 'vertical, out of plane' -- 
                # that is, put it into normal XYZ rectangular coords, so we can use RECLAT etc on it.
                
                if ('MU69' in name_target):  # Was 0 2 1. But this makes tunacan radius look in wrong dir.
                                             # 201 looks same
                                             # 210 similar
                                             # 201 similar
                                             # 102, 120 similar.
                                             # ** None of these change orientation of 'radius' backplane. OK.
                                             
                    pt_intersect_frame = np.array([pt_intersect_frame[0], pt_intersect_frame[2], pt_intersect_frame[1]])
                
                # Get the radius and azimuth of the intersect, in the ring plane
                # Q: Why for the TUNACAN is the radius zero here along horizontal (see plot)?
                # A: Ahh, it is not zero. It is just that the 'projected radius' of a ring that is nearly edge-on
                # can be huge! Basically, if we try to calc the intersection with that plane, it will give screwy
                # answers, because the plane is so close to edge-on that intersection could be a long way 
                # from body itself.
                
                # Instead, I really want to take the tangent sky plane, intersect that, and then calc the 
                # position of that (in xyz, radius, longitude, etc).
                # Since that plane is fixed, I don't see a disadvantage to doing that.
                
                # We want the 'radius' to be the radius in the equatorial plane -- that is, sqrt(x^2 + y^2).
                # We don't want it to be the 'SPICE radius', which is the distance.
                # (For MU69 equatorial plane is nominally XZ, but we have already changed that above to XY.)
                
                _radius_3d, lon, lat = sp.reclat(pt_intersect_frame)
                
                radius_eq = sp.vnorm([pt_intersect_frame[0], pt_intersect_frame[1], 0])  
#                radius_eq = sp.vnorm([pt_intersect_frame[0], pt_intersect_frame[1], pt_intersect_frame[2]])
                
                # Get the vertical position (altitude)
                
                altitude = pt_intersect_frame[2]

                # Calculate the phase angle: angle between s/c-to-ring, and ring-to-sun
        
                vec_ring_sun_frame = -pt_intersect_frame + vec_target_sun_frame
                
                angle_phase = sp.vsep(-vec_pix_frame, vec_ring_sun_frame)
                
                # Save various derived quantities
                         
                radius_arr[i_y, i_x] = radius_eq
                lon_arr[i_y, i_x]    = lon
                phase_arr[i_y, i_x]  = angle_phase
                altitude_arr[i_y, i_x] = altitude
                
                # Save these just for debugging
                
                x_arr[i_y, i_x] = pt_intersect_frame[0]
                y_arr[i_y, i_x] = pt_intersect_frame[1]
                z_arr[i_y, i_x] = pt_intersect_frame[2]
                
                # Now calc angular separation between this pixel, and the satellites in our list
                # Since these are huge arrays, cast into floats to make sure they are not doubles.
                
                if (name_body.upper() == 'JUPITER'):
                    ang_thebe_arr[i_y, i_x]    = sp.vsep(vec_pix_j2k, vec_thebe_j2k)
                    ang_adrastea_arr[i_y, i_x] = sp.vsep(vec_pix_j2k, vec_adrastea_j2k)
                    ang_metis_arr[i_y, i_x]    = sp.vsep(vec_pix_j2k, vec_metis_j2k)
                    ang_amalthea_arr[i_y, i_x] = sp.vsep(vec_pix_j2k, vec_amalthea_j2k) 

        # Now, fix a bug. The issue is that SP.INRYPL uses the actual location of the bodies (no aberration),
        # while their position is calculated (as it should be) with abcorr=LT. This causes a small error in the 
        # positions based on the INRYPL calculation. This should probably be fixed above, but it was not 
        # obvious how. So, instead, I am fixing it here, by doing a small manual offset.
        
        # Calculate the shift required, by measuring the position of MU69 with abcorr=NONE, and comparing it to 
        # the existing calculation, that uses abcorr=LT. This is brute force, but it works. For MU69 approach, 
        # it is 0.75 LORRI 4x4 pixels (ie, 3 1X1 pixels). This is bafflingly huge (I mean, we are headed
        # straight toward MU69, and it takes a month to move a pixel, and RTLT is only a few minutes). But I have
        # confirmed the math and the magnitude, and it works.
        
        (st_sc_target_j2k_nolt, _)                 = sp.spkezr(name_target,   et, 'J2000', 'NONE', name_observer)
        vec_sc_target_j2k_nolt                     = st_sc_target_j2k_nolt[0:3]
        (_, ra_sc_target_nolt, dec_sc_target_nolt) = sp.recrad(vec_sc_target_j2k_nolt)

        (x0,y0) = w.wcs_world2pix(ra_sc_target_nolt*hbt.r2d, dec_sc_target_nolt*hbt.r2d, 1)
        (x1,y1) = w.wcs_world2pix(ra_sc_target     *hbt.r2d, dec_sc_target     *hbt.r2d, 1)
        dx = x1-x0
        dy = y1-y0
        
        print(f'Compute backplanes: INRYPL pixel shift = {dx}, {dy}')

        dx_int = int(round(dx))
        dy_int = int(round(dy))
        
        do_roll = True
        
        if do_roll:
            print(f'compute_backplanes: Rolling by {dx_int}, {dy_int} due to INRYPL')
             
            # Now shift all of the planes that need fixing. The dRA_km and dDec_km are calculated before INRYPL()
            # is applied, so they do not need to be shifted. I have validated that by plotting them.
            # 
            # XXX NP.ROLL() is really not ideal. I should use a function that introduces NaN at the edge, not roll it.
            
            radius_arr   = np.roll(np.roll(radius_arr,   dy_int, axis=0), dx_int, axis=1)
            lon_arr      = np.roll(np.roll(lon_arr,      dy_int, axis=0), dx_int, axis=1)
            phase_arr    = np.roll(np.roll(phase_arr,    dy_int, axis=0), dx_int, axis=1)
            altitude_arr = np.roll(np.roll(altitude_arr, dy_int, axis=0), dx_int, axis=1)
        else:
            print(f'compute_backplanes: Skipping roll due to INRYPL, based on do_roll={do_roll}')
            
        # Assemble the results into a backplane
    
        backplane = {
             'RA'           : ra_arr.astype(float),  # return radians
             'Dec'          : dec_arr.astype(float), # return radians 
             'dRA_km'       : dra_arr.astype(float),
             'dDec_km'      : ddec_arr.astype(float),
             'Radius_eq'    : radius_arr.astype(float),
             'Longitude_eq' : lon_arr.astype(float), 
             'Phase'        : phase_arr.astype(float),
             'Altitude_eq'  : altitude_arr.astype(float),
#             'x'            : x_arr.astype(float),
#             'y'            : y_arr.astype(float),
#             'z'            : z_arr.astype(float),
#             
             }
        
        # Assemble a bunch of descriptors, to be put into the FITS headers
        
        desc = {
                'RA of pixel, radians',
                'Dec of pixel, radians',
                'Offset from target in target plane, RA direction, km',
                'Offset from target in target plane, Dec direction, km',
                'Projected equatorial radius, km',
                'Projected equatorial longitude, km',
                'Sun-target-observer phase angle, radians',
                'Altitude above midplane, km',    
#                'X position of sky plane intercept',
#                'Y position of sky plane intercept',
#                'Z position of sky plane intercept'
                }
                
        # In the case of Jupiter, add a few extra fields
        
        if (name_body.upper() == 'JUPITER'):
            backplane['Ang_Thebe']    = ang_thebe_arr.astype(float)   # Angle to Thebe, in radians
            backplane['Ang_Metis']    = ang_metis_arr.astype(float)
            backplane['Ang_Amalthea'] = ang_amalthea_arr.astype(float)
            backplane['Ang_Adrastea'] = ang_adrastea_arr.astype(float)
    
        # If distance to any of the small sats is < 0.3 deg, then delete that entry in the dictionary
        
            if (np.amin(ang_thebe_arr) > fov_lorri):
                del backplane['Ang_Thebe']
            else:
                print("Keeping Thebe".format(np.min(ang_thebe_arr) * hbt.r2d))
        
            if (np.amin(ang_metis_arr) > fov_lorri):
                del backplane['Ang_Metis']
            else:
                print("Keeping Metis, min = {} deg".format(np.min(ang_metis_arr) * hbt.r2d))
                
            if (np.amin(ang_amalthea_arr) > fov_lorri):
                del backplane['Ang_Amalthea']
            else:
                print("Keeping Amalthea, min = {} deg".format(np.amin(ang_amalthea_arr) * hbt.r2d))
        
            if (np.amin(ang_adrastea_arr) > fov_lorri):
                del backplane['Ang_Adrastea']
            else:
                print("Keeping Adrastea".format(np.min(ang_adrastea_arr) * hbt.r2d))
        
    # And return the backplane set
                 
    return (backplane, desc)
コード例 #26
0
def get_fits_info_from_files_lorri(
        path,
        file_tm="/Users/throop/gv/dev/gv_kernels_new_horizons.txt",
        pattern=''):
    "Populate an astropy table with info from the headers of a list of LORRI files."
    import numpy as np
    import spiceypy as sp
    import glob
    import astropy
    from astropy.io import fits
    from astropy.table import Table
    import astropy.table
    import math
    import hbt

    # For testing:
    # file = '/Users/throop/Data/NH_Jring/data/jupiter/level2/lor/all/lor_0035020322_0x630_sci_1.fit' # 119 deg phase as per gv
    # file = '/Users/throop/Data/NH_Jring/data/jupiter/level2/lor/all/lor_0034599122_0x630_sci_1.fit' # 7 deg phase, inbound

    # t = hbt.get_fits_info_from_files_lorri(file)

    # Flags: Do we do all of the files? Or just a truncated subset of them, for testing purposes?

    DO_TRUNCATED = False
    NUM_TRUNC = 100

    # We should work to standardize this, perhaps allowing different versions of this function
    # for different instruments.

    d2r = np.pi / 180.
    r2d = 1. / d2r

    sp.furnsh(file_tm)

    # *** If path ends with .fit or .fits, then it is a file not a path. Don't expand it, but read it as a single file.

    if (('.fits' in path) or ('.fit' in path)):
        file_list = path
        files = [file_list]

    else:

        dir_data = path
        #dir_data = '/Users/throop/data/NH_Jring/data/jupiter/level2/lor/all'
        # Start up SPICE

        # Get the full list of files
        # List only the files that match an (optional) user-supplied pattern, such as '_opnav'

        file_list = glob.glob(dir_data + '/*' + pattern + '.fit')
        files = np.array(file_list)
        indices = np.argsort(file_list)
        files = files[indices]

# Read the JD from each file. Then sort the files based on JD.

    jd = []
    for file in files:
        hdulist = fits.open(file)
        jd.append(hdulist[0].header['MET'])
        hdulist.close()

    fits_met = []  # new list (same as array)
    fits_startmet = []
    fits_stopmet = []
    fits_exptime = []  # starting time of exposure
    fits_target = []
    fits_reqdesc = []
    fits_reqcomm = []  # New 9-Oct-2018
    fits_reqid = []  # New 9-Oct-2018
    fits_spcinst0 = []
    fits_spcutcjd = []
    fits_naxis1 = []
    fits_naxis2 = []
    fits_sformat = []  # Data format -- '1x1' or '4x4'
    fits_spctscx = []  # sc - target, dx
    fits_spctscy = []  # dy
    fits_spctscz = []  # dz
    fits_spctcb = []  # target name
    fits_spctnaz = [
    ]  # Pole angle between target and instrument (i.e., boresight rotation angle)
    fits_rsolar = [
    ]  # (DN/s)/(erg/cm^2/s/Ang/sr), Solar spectrum. Use for resolved sources.

    if (DO_TRUNCATED):
        files = files[0:NUM_TRUNC]

#files_short = np.array(files)
#for i in range(files.size):
#    files_short = files[i].split('/')[-1]  # Get just the filename itself

# Set up one iteration variable so we don't need to create it over and over
    num_obs = np.size(files)
    i_obs = np.arange(num_obs)

    print("Read " + repr(np.size(files)) + " files.")

    for file in files:
        print("Reading file " + file)

        hdulist = fits.open(file)
        header = hdulist[0].header

        keys = header.keys()

        fits_met.append(header['MET'])
        fits_exptime.append(header['EXPTIME'])
        fits_startmet.append(header['STARTMET'])
        fits_stopmet.append(header['STOPMET'])
        fits_target.append(header['TARGET'])
        fits_reqdesc.append(header['REQDESC'])
        fits_reqcomm.append(header['REQCOMM'])
        fits_reqid.append(header['REQID'])
        fits_spcinst0.append(header['SPCINST0'])
        fits_spcutcjd.append(
            (header['SPCUTCJD'])[3:])  # Remove the 'JD ' from before number
        fits_naxis1.append(header['NAXIS1'])
        fits_naxis2.append(header['NAXIS2'])
        fits_spctscx.append(header['SPCTSCX'])
        fits_spctscy.append(header['SPCTSCY'])
        fits_spctscz.append(header['SPCTSCZ'])
        fits_spctnaz.append(header['SPCTNAZ'])
        fits_sformat.append(header['SFORMAT'])
        fits_rsolar.append(
            header['RSOLAR']
        )  # NB: This will be in the level-2 FITS, but not level 1

        hdulist.close()  # Close the FITS file

#print object
#print "done"

# Calculate distance to Jupiter in each of these
# Calc phase angle (to Jupiter)
# Eventually build backplanes: phase, RA/Dec, etc.
# Eventually Superimpose a ring on top of these
#  ** Not too hard. I already have a routine to create RA/Dec of ring borders.
# Eventually overlay stars
#   Q: Will there be enough there?
# Eventually repoint based on stars
#  ** Before I allow repointing, I should search a star catalog and plot them.

# Convert some things to numpy arrays. Is there any disadvantage to this?

    met = np.array(fits_met)
    jd = np.array(fits_spcutcjd,
                  dtype='d')  # 'f' was rounding to one decimal place...
    naxis1 = np.array(fits_naxis1)
    naxis2 = np.array(fits_naxis2)
    target = np.array(
        fits_target
    )  # np.array can use string arrays as easily as float arrays
    instrument = np.array(fits_spcinst0)
    dx_targ = np.array(fits_spctscx)
    dy_targ = np.array(fits_spctscy)
    dz_targ = np.array(fits_spctscz)
    desc = np.array(fits_reqdesc)
    reqid = np.array(fits_reqid)
    reqcomm = np.array(fits_reqcomm)
    met0 = np.array(fits_startmet)
    met1 = np.array(fits_stopmet)
    exptime = np.array(fits_exptime)
    rotation = np.array(fits_spctnaz)
    sformat = np.array(fits_sformat)
    rotation = np.rint(rotation).astype(
        int
    )  # Turn rotation into integer. I only want this to be 0, 90, 180, 270...
    rsolar = np.array(fits_rsolar)

    files_short = np.zeros(num_obs, dtype='U60')

    # Now do some geometric calculations and create new values for a few fields

    dist_targ = np.sqrt(dx_targ**2 + dy_targ**2 + dz_targ**2)

    phase = np.zeros(num_obs)
    utc = np.zeros(num_obs, dtype='U30')
    et = np.zeros(num_obs)
    subsclat = np.zeros(num_obs)  # Sub-sc latitude
    subsclon = np.zeros(num_obs)  # Sub-sc longitude

    name_observer = 'New Horizons'
    frame = 'J2000'
    abcorr = 'LT+S'
    #         Note that using light time corrections alone ("LT") is
    #         generally not a good way to obtain an approximation to an
    #         apparent target vector:  since light time and stellar
    #         aberration corrections often partially cancel each other,
    #         it may be more accurate to use no correction at all than to
    #         use light time alone.

    # Fix the MET. The 'MET' field in fits header is actually not the midtime, but the time of the first packet.
    # I am going to replace it with the midtime.
    # *** No, don't do that. The actual MET field is used for timestamping -- keep it as integer.

    #    met = (met0 + met1) / 2.

    # Loop over all images

    for i in i_obs:

        # Get the ET and UTC, from the JD. These are all times *on s/c*, which is what we want

        et[i] = sp.utc2et('JD ' + repr(jd[i]))
        utc[i] = sp.et2utc(et[i], 'C', 2)

        # Calculate Sun-Jupiter-NH phase angle for each image

        (st_jup_sc, ltime) = sp.spkezr('Jupiter', et[i], frame, abcorr,
                                       'New Horizons')  #obs, targ
        (st_sun_jup, ltime) = sp.spkezr('Sun', et[i], frame, abcorr, 'Jupiter')
        ang_scat = sp.vsep(st_sun_jup[0:3], st_jup_sc[0:3])
        phase[i] = math.pi - ang_scat
        #      phase[i] = ang_scat
        files_short[i] = files[i].split('/')[-1]
        # Calc sub-sc lon/lat

        mx = sp.pxform(frame, 'IAU_JUPITER', et[i])
        st_jup_sc_iau_jup = sp.mxv(mx, st_jup_sc[0:3])

        (radius, subsclon[i],
         subsclat[i]) = sp.reclat(st_jup_sc[0:3])  # Radians
        (radius, subsclon[i],
         subsclat[i]) = sp.reclat(st_jup_sc_iau_jup)  # Radians


# Stuff all of these into a Table

    t = Table([
        i_obs, met, utc, et, jd, files, files_short, naxis1, naxis2, target,
        instrument, dx_targ, dy_targ, dz_targ, reqid, met0, met1, exptime,
        phase, subsclat, subsclon, naxis1, naxis2, rotation, sformat, rsolar,
        desc, reqcomm
    ],
              names=('#', 'MET', 'UTC', 'ET', 'JD', 'Filename', 'Shortname',
                     'N1', 'N2', 'Target', 'Inst', 'dx', 'dy', 'dz', 'ReqID',
                     'MET Start', 'MET End', 'Exptime', 'Phase', 'Sub-SC Lat',
                     'Sub-SC Lon', 'dx_pix', 'dy_pix', 'Rotation', 'Format',
                     'RSolar', 'Desc', 'Comment'))

    # Define units for a few of the columns

    t['Exptime'].unit = 's'
    t['Sub-SC Lat'].unit = 'degrees'

    # Create a dxyz_targ column, from dx dy dz. Easy!

    t['dxyz'] = np.sqrt(t['dx']**2 + t['dy']**2 +
                        t['dz']**2)  # Distance, in km

    return t
コード例 #27
0
ファイル: gaiaif_util.py プロジェクト: drbitboy/gaiaif
  def __init__(self,fovraws,ralohi=(),declohi=()
              ,obs_pos=None,obs_vel=None,obs_year=None
              ):
    """Convert FOV definition in external inertial reference frame to an
FOV definition in a local reference frame; also determine RA,Dec limits
of FOV for use in star catalog lookup.

For polygonal FOVs, set up FOV as vectors in a local reference frame,
with a matrix to rotate vectors from the external to the local frame.
The local reference frame (reffrm) will have +Z along the average
vertices' direction, and will be rotated such the the +X axis is not
parallel to any of the edges of the polygon from the FOV projected onto
the Z=+1 plane.

Arguments

  fovraws - sequence either of vector and cone and half-angle for
            circular FOV, or of a pair of vectors for RA,Dec box, or
            of three or more vectors for a polygonal FOV.  A vector is
            a sequence either of two values, RA,Dec, or of three values,
            X,Y,Z.

  obs_pos - Observer position, solar system barycentric, 3-vector, km
            - For parallax correction

  obs_vel - Observer velocity, solar system barycentric, 3-vector, km/s
            - For proper motion correction

  obs_year - Observer time, y past 2015.5 (Gaia DR2 epoch)
            - For stellar aberration correction

"""
    ### Get count of items in FOV sequence; ensure it is 2 or more
    ### and ralohi and declohi are empty, or that fovraws is empty
    ### and ralohi and declohi have 2 values each
    (self.fovraws
    ,self.ralohi
    ,self.declohi
    ,self.obs_pos
    ,self.obs_vel
    ,self.obs_year
    ,)= fovraws,list(ralohi),list(declohi),obs_pos,obs_vel,obs_year
    self.L = len(fovraws)
    assert (1<self.L and not (self.ralohi+self.declohi)
      ) or (0==self.L and 2==len(self.ralohi) and 2==len(self.declohi)
      ), 'Invalid vertices in FOV'

    ################################
    ### Initialize:  FOV RA,Dec pairs; FOV type (assume polygon); FOV
    ###              vector triples; list of RA,Dec boxes
    self.radecdegs = list()
    self.fovtype = 1<self.L and FOV.POLYGONTYPE or FOV.RADECBOXTYPE
    self.uvfovxyzs,fovsum = list(),sp.vpack(0.,0.,0.)
    self.radec_boxes = list()
    rdba = self.radec_boxes.append   ### Shorthand to append box to list

    ################################
    ### Parse list of vertices:
    ### - [list,float]          =>  Circle (cone)
    ### - [list,list]           =>  RA,Dec box
    ### - [list,list,list,...]  =>  Polygon
    for vertex in fovraws:

      ### For second of two vertices ...
      if 1==len(self.radecdegs) and 2==self.L:
        ### Two-vertex items are either a conic FOV, or an [RA,Dec] box
        try:
          ### If second item in list is a float, then it's a half-angle
          ### of the cone
          self.hangdeg = float(vertex)
          assert self.hangdeg < 90.0,'Cone half-angle is not less than 90degrees'
          assert self.hangdeg > 0.0,'Cone half-angle is not greater than 0degrees'
          self.hangrad = self.hangdeg * rpd
          self.min_cosine = math.cos(self.hangrad)
          self.uv_cone_axis = self.uvfovxyzs[0]
          self.fovtype = FOV.CIRCLETYPE
          break
        except AssertionError as e:
          raise
        except:
          ### If the above fails, then it's the second corner of the box
          self.fovtype = FOV.RADECBOXTYPE

      ### Parse one vertex
      ra,dec,uvxyz = parse_inertial(vertex)

      ### Append RA,Dec and unit vector XYZ onto their resepective lists
      self.radecdegs.append((ra,dec,))
      self.uvfovxyzs.append(uvxyz)
      fovsum = sp.vadd(fovsum,uvxyz)

    ################################
    ### Calculate RA,DEC limits as list of [ralo,rahi,declo,dechi] boxes
    ### - .radec_boxes is a list; rdba is .radec_boxes.append
    ### - List will have multiple RA,Dec boxes if FOV crosses the Prime
    ###   Meridian (PM) an even number of times.

    if self.fovtype == FOV.RADECBOXTYPE:
      ### RA,DEC box FOV:  calculate limits; handle PM crossing
      if 2==self.L:
        ras,decs = zip(*self.radecdegs)
        ralo,rahi = sorted(ras)
        declo,dechi = sorted(decs)
        if 180 > (rahi-ralo):
          rdba([ralo,rahi,declo,dechi])
        else:
          rdba([0.0,ralo,declo,dechi])
          rdba([rahi,360.0,declo,dechi])
      else:
        if self.ralohi[1] > self.ralohi[0]:
          rdba(self.ralohi+self.declohi)
        else:
          rdba([self.ralohi[0],360.0]+self.declohi)
          rdba([0.0,self.ralohi[1]]+self.declohi)

    elif self.fovtype == FOV.CIRCLETYPE:
      ### Circular FOV:  DEC limits determine RA limits; handle PM Xing
      ra,dec = self.radecdegs[0]
      fovdeclo = dec - self.hangdeg
      fovdechi = dec + self.hangdeg

      if fovdeclo < -90.0 or fovdechi > 90.0:
        ### A pole is in the FOV; use full RA range
        fovralo,fovrahi = 0.0,360.0
        fovdeclo,fovdechi = max([fovdeclo,-90.0]),min([fovdechi,+90.0])

      elif fovdeclo == -90.0 or fovdechi == 90.0:
        ### A pole is on the FOV circumference; RA range is 180 degrees
        fovralo,fovrahi = ra-90.0,ra+90.0

      else:
        ### The FOV excludes the poles; calculate the RA range, using
        ### the formula validated in script validate_delta_ra_formula.py
        tanhang,tandec = math.tan(self.hangrad),math.tan(dec*rpd)
        sinhang,cosdec = math.sin(self.hangrad),math.cos(dec*rpd)
        coshang = math.cos(self.hangrad)
        T = sinhang / math.sqrt(1.0 - ((tanhang*tandec)**2))
        deltara = dpr * math.atan(T / (cosdec * coshang))
        fovralo,fovrahi = ra-deltara,ra+deltara

      ### Ensure RA limits are within range [0:360] (N.B. inclusive)
      if fovralo < 0.0: fovralo += 360.0
      if fovrahi > 360.0: fovrahi -= 360.0

      if fovralo <= fovrahi:
        ### RA lo <= RA hi:  no PM crosssing
        rdba([fovralo,fovrahi,fovdeclo,fovdechi])
      else:
        ### RA hi < RA hi:  there is a PM crosssing
        rdba([0.0,fovrahi,fovdeclo,fovdechi])
        rdba([fovralo,360.,fovdeclo,fovdechi])

    else:
      assert self.fovtype == FOV.POLYGONTYPE
      ### Polygonal FOV:  build frame where all vertices will be
      ### projected onto the plane Z=1

      ### .uvavg:  unit vector = mean of all vertices, will be +Z
      self.uvavg = sp.vhat(fovsum)

      ### Create rotation matrix to FOV frame:  +Z is mean of vertices'
      ###   directions (.uvavg); +X will be a direction that is not
      ###   parallel to any side of the polygon
      ### - Start with temporary matrix with +Z as defined above; +X
      ###   toward vertex at largest angle from .uvavg
      vother = min([(sp.vdot(self.uvavg,v),list(v),) for v in self.uvfovxyzs])[1]
      tmpmtx = sp.twovec(self.uvavg,3,vother,1)
      ### - Rotate all vectors to that frame; scale Z components to 1.0
      vtmps = list()
      for v in self.uvfovxyzs:
        ### - Ensure all vertices are in the same hemisphere
        assert 0.0 < sp.vdot(self.uvavg,v),'All vertices are not in the same hemisphere'
        vtmp = sp.mxv(tmpmtx,v)
        vtmps.append(sp.vscl(1.0/vtmp[2],vtmp))

      ### Find largest azimuth gap between any two sides:  that azimuth
      ###   will be direction of +X in the final rotation matrix
      ### - Get azimuths of all sides of polygon, in range [-PI:PI]
      azimuths,vlast = list(),vtmps[-1]
      for v in self.uvfovxyzs:
        azimuths.append(numpy.arctan((v[1]-vlast[1])/(v[0]-vlast[0])))
        vlast = v
      ### - Sort angles and add [least angle plus PI] to end of list
      azimuths.sort()
      azimuths.append(azimuths[0]+sp.pi())
      ### - Find largest delta-azimuth and its index
      dazimuths = [hi-lo for hi,lo in zip(azimuths[1:],azimuths[:-1])]
      maxdaz = max(dazimuths)
      imaxdaz = dazimuths.index(maxdaz)
      ### - Calculate azimuth from to mean of that delta-azimuth,
      meanaz = azimuths[imaxdaz] + (maxdaz / 2.0)

      ### Final matrix:  add rotation of tmpmtx around +Z by that angle
      self.mtxtofov = sp.mxm(sp.rotate(meanaz,3),tmpmtx)

      ### Apply final rotation matrix, store results in .uvlclxyzs
      tmpmtx = sp.twovec(self.uvavg,3,vother,1)
      self.uvlclxyzs = [self.rotate_to_local(v) for v in self.uvfovxyzs]

      ### Calculate upper and lower RA and Dec limits, with PM crossings
      los,his = list(),list()
      ### - Create [[RA,Dec],[X,Y,Z]] pairs list; ensure last is off PM
      pairs = list(zip(self.radecdegs,self.uvfovxyzs))
      pop_count = 0
      while pairs[-1][0][0] == 0.0:
        pop_count += 1
        assert pop_count < self.L,'All vertices are on the Prime Meridian'
        pairs.append(pairs.pop(0))

      ### Count PM crossings
      self.crossing_count = 0
      lastra = pairs[-1][0][0]
      zero_count = 0
      for (ra,dec,),xyz in pairs:
        if ra == 0.0:
          zero_count += 1
          if lastra > 180.0: ra = 360.0
        if 180 < abs(ra-lastra): self.crossing_count += 1
        lastra = ra

      if 0==self.crossing_count or 1==(1&self.crossing_count):
        ### If there are either no, or an odd number, of PM crossings,
        ### then use the pairs as-is for a single FOV
        subfovs = [pairs]
        if self.crossing_count:
          ### - For odd crossing count, one pole or the other must be
          ###   in the FOV; init full RA range, that pole for Dec ranges
          ralo,rahi = 0.0,360.0
          if sp.vdot(self.uvavg,[0,0,1]) > 0.0: declo = dechi = +90.0
          else                                : declo = dechi = -90.0
        else:
          ### - For zero crossing count, initialize inverted ranges
          ralo,rahi = 360.0,0.0
          declo,dechi = +90.0,-90.0
        subranges = [[ralo,rahi,declo,dechi]]

      else:
        ### If there are an even, non-zero number of PM crossings, break
        ### them into two sub-FOVs, one on either side of the PM

        eastfov,westfov = list(),list()

        if zero_count:
          ### If there are any zero RA values, rotate the pairs to
          ### ensure a zero-RA pair is the first, so it and the non-zero
          ### last pair will be assigned to the correct side of the PM
          while pairs[0][0][0]!=0.0: pairs.append(pairs.pop(0))
        else:
          ### If there are no zero RA values, rotate the pairs to ensure
          ### a crossing occurs between the last and first pair, so the
          ### corresponding zero crossing will be assigned to the
          ### correct side of the PM
          while abs(pairs[0][0][0]-pairs[-1][0][0])<180:
            pairs.append(pairs.pop(0))

        ### Write vertices into the two sub-FOVs

        ### - Set last-vertex values for first item in pairs
        (lastra,lastdec,),lastxyz = pairs[-1]

        for pair in pairs:
          ### - Loop over vertex pairs ((RA,DEC,),Cartesian_Vector)
          (ra,dec,),xyz = pair

          if ra == 0.0:

            ### - When RA=0, the previous RA determines if it's 0 ar 360
            if lastra >= 180.0:
              ra = 360.0
              westfov.append([(ra,dec,),xyz])
              iswest = True
            else:
              eastfov.append(pair)
              iswest = False

          elif abs(lastra-ra) >= 180.0:

            ### - When the change in RA>=180, the PM is being crossed

            ### - Find the mid-vector where the PM is crossed
            k1 = -xyz[1] / (lastxyz[1]-xyz[1])
            midxyz = sp.vhat(sp.vlcom(1.0-k1,xyz,k1,lastxyz))
            middec = dpr * sp.recrad(midxyz)[2]

            ### - Add that mid-vector, with RA=360, to the west FOV
            westfov.append([(360.0,middec,),midxyz])

            ### - Determine if vector is west
            iswest = ra >= 180.0

            ### - Add that mid-vector, with RA=0, to the east FOV ...
            if (ra > 0.0) and (not iswest):
              ### - ... only if the ra is not already 0, as it will be
              ###       added in the next step
              eastfov.append([(0.0,middec,),midxyz])

            ### Add the vector to either east or west FOV
            if iswest: westfov.append(pair)
            else     : eastfov.append(pair)

          else:

            ### PM was not crossed, add vector to same FOV, as last time
            if iswest: westfov.append(pair)
            else     : eastfov.append(pair)

          ### - Set last-vertex values for next item in pairs
          (lastra,lastdec,),lastxyz = (ra,dec,),xyz

        ### - Create subfovs list of east and west FOVs; set subranges
        subfovs = [eastfov,westfov]
        subranges = [[360.0,0.0,90.0,-90.0],[360.0,0.0,90.0,-90.0]]

      ### To here, we have list of FOV(s) and list of range(s); use them
      ### to determine RA,DEC box(es) to use for database query

      while subfovs:

        ### Get sub-FOV, sub-range; set last vertex's XYZ
        subfov,(ralo,rahi,declo,dechi,) = subfovs.pop(),subranges.pop()
        lastxyz = subfov[-1][-1]

        for pair in subfov:
          ### Each element of subfov comprises (RA,Dec) and vertex XYZ
          ### - xyz is a unit vector
          (ra,dec,),xyz = pair

          ### - Adjust RA limits as needed from RA of vertex
          if   ra > rahi: rahi = ra
          elif ra < ralo: ralo = ra

          ### - Set Dec extrema from DEC of vertex
          maxdec = mindec = dec

          ### - Calculate Dec extrema from lastxyz to xyz
          ### -- Normal to plane of lastxyz and syz
          sidenormal = sp.vcrss(lastxyz,xyz)
          ### -- Z-rates along great circle at lastxyz and at xyz
          lastdz = sp.vcrss(sidenormal,lastxyz)[2]
          dz = sp.vcrss(sidenormal,xyz)[2]
          if 0.0 > (lastdz*dz):
            ### -- If sign of Z-rates differs, there should be an
            ###    extreme value between lastxyz and xyz
            ### --- Get vector perpendicular to side normal on equator
            ### --- Use that to calculate the unit vector at Dec extreme
            equinox = sp.vcrss([0,0,1],sidenormal)
            vtoextremez = sp.ucrss(sidenormal,equinox)
            ### --- Cosine of angle between lastxyz and xyz
            mindot = sp.vdot(lastxyz,xyz)
            for none in [None,None]:
              ### --- Two cases:  vtoextremez and -vtoextremez
              ###     - Angles from vtoextremez to lastxyz and to xyz
              ###       must be less than angle between lastxyz and xyz
              ###       so cosines of those angles must be greater
              lastxyzdot = sp.vdot(lastxyz,vtoextremez)
              xyzdot = sp.vdot(xyz,vtoextremez)
              if lastxyzdot>mindot and xyzdot>mindot:
                ### --- Adjust maxdec and mindec as needed
                try   : extremedec = dpr * math.asin(vtoextremez[2])
                except: extremedec = dpr * sp.recrad(vtoextremez)[2]
                if   extremedec > maxdec: maxdec = extremedec
                elif extremedec < mindec: mindec = extremedec
                break
              ### --- Invert vtoextremez for next pass
              vtoextremez = sp.vminus(vtoextremez)

          ### - Adjust Dec limits as needed from Dec extrema of side
          if maxdec > dechi: dechi = maxdec
          if mindec < declo: declo = mindec
          lastxyz = xyz

        ### Append calculated RA,Dec box(es)
        rdba((ralo,rahi,declo,dechi,))

      ### Put None in .localxyzs, in .v_for_stellar_aberr, and in
      ### .v_for_parallax; if no stellar aberration or parallax is
      ### explicitly applied to define it later, then .localxyzs will be
      ### calculated on the fly
      self.localxyzs = None
      self.v_for_stellar_aberr = None
      self.v_for_parallax = None
コード例 #28
0
ファイル: build.py プロジェクト: dstansby/Ikuchi
                           4332.59 * 86400.0 * 1e3),
    'Uranus': make_planet('IAU_URANUS', 'USO', len(t),
                          30688.5 * 86400.0 * 1e3),
    'Saturn': make_planet('IAU_SATURN', 'KSO', len(t),
                          10759.22 * 86400.0 * 1e3)
}

# Do the calculations.
print('[ikuchi-build] Calculating pole positions')
for i in range(len(t)):
    if i % 1000 == 0:
        print('[ikuchi-build] Calculating pole positions: {:04}/{:04}'.format(
            i + 1, len(t)))
    for p in data:
        m = spiceypy.pxform(data[p]['s'], data[p]['d'], t[i])
        v = spiceypy.mxv(m, [0, 0, 1])
        data[p]['v'][i, :] = v
        data[p]['roty'][i] = np.arccos(v[2]) * 180 / np.pi
        data[p]['rotz'][i] = np.arctan2(v[1], v[0]) * 180 / np.pi

# Now we have a set of angles that are modulo 2pi, but to fit them we need
# to remove the modulo 2pi and have a monotonic set of angles, e.g., 0- 2*pi*n.
# We find all the points where the angle changes and then add an offset to that
# rotation. We keep increasing that offset by 2pi for each rotation to
# get a monotonic change.
print(
    '[ikuchi-build] Adjusting rotation angles to be monotonic (invert modulo 2pi)'
)
for p in data:
    rages = []
    st = 0
コード例 #29
0
ファイル: gaiaif_util.py プロジェクト: drbitboy/gaiaif
 def rotate_to_local(self,vxyz):
   """Utility to rotation from inertial frame to local frame"""
   return sp.mxv(self.mtxtofov,vxyz)