Example #1
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
Example #2
0
def cassini_surfintercerpt(utc, output=False):
    target = 'TITAN'
    fixref = 'IAU_TITAN'
    dref = 'IAU_TITAN'
    method = 'ELLIPSOID'
    abcorr = 'NONE'
    obsrvr = 'CASSINI'
    state = cassini_phase(utc)
    dvec = spice.vhat(-state[:3])

    et = spice.str2et(utc)

    [point, trgepc, srfvec] = spice.sincpt(method, target, et, fixref, abcorr,
                                           obsrvr, dref, dvec)
    temp = spice.reclat(point)

    radius = temp[0]
    colat = 90 + spice.convrt(temp[1], "RADIANS", "DEGREES")
    lon = np.mod(spice.convrt(temp[2], "RADIANS", "DEGREES"), 360)

    if output:
        print("Distance to Intercept Point", spice.vnorm(srfvec))
        print("Radius", radius)
        print("Colatitude", colat)
        print("Longtitude", lon)

    return point, [radius, colat, lon], spice.vnorm(srfvec)
Example #3
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()
Example #4
0
    def point_towards_sun(self, pixel_res=0.5):
        """
        Compute the solar azimuth.

        Pixel resolution is required to stay within one pixel of the origin
        point
        """
        # Check if surface point spoint was set
        if not self.spoint_set:
            raise SPointNotSetError
        # Get the difference vector poB=subsolar-origin with its tail at origin
        # and its head at the subsolar point
        poB = spice.vsub(self.subsolar, self.spoint)

        # get pixel scale in km/pixel and then divide by 2 to insure to stay
        # within a pixel of the origin point
        scale = (pixel_res / 1000.0) / 2.0

        # the difference vector cuts through the body,
        # we need the tangent vector
        # to the surface at the origin point. vperp receives the perpendicular
        # component of the poB towards the spoint vector
        hpoB = spice.vperp(poB, self.spoint)
        # unitize the tangent vector and then scale it to within a pixel of the
        # origin point
        upoB = spice.vhat(hpoB)
        spoB = spice.vscl(scale, upoB)

        # Compute the new point in body fixed. This point will be within a
        # pixel of the origin but in the same direction as the requested la/lon
        # of the point of interest, i.e. the subsolar point
        nB = spice.vadd(self.spoint, spoB)

        coords = Coords.fromtuple(spice.reclat(nB))
        return coords.dlon, coords.dlat
Example #5
0
    def point_towards_sun(self, pixel_res=0.5):
        """
        Compute the solar azimuth.

        Pixel resolution is required to stay within one pixel of the origin
        point
        """
        # Check if surface point spoint was set
        if not self.spoint_set:
            raise SPointNotSetError
        # Get the difference vector poB=subsolar-origin with its tail at origin
        # and its head at the subsolar point
        poB = spice.vsub(self.subsolar, self.spoint)

        # get pixel scale in km/pixel and then divide by 2 to insure to stay
        # within a pixel of the origin point
        scale = (pixel_res / 1000.0) / 2.0

        # the difference vector cuts through the body,
        # we need the tangent vector
        # to the surface at the origin point. vperp receives the perpendicular
        # component of the poB towards the spoint vector
        hpoB = spice.vperp(poB, self.spoint)
        # unitize the tangent vector and then scale it to within a pixel of the
        # origin point
        upoB = spice.vhat(hpoB)
        spoB = spice.vscl(scale, upoB)

        # Compute the new point in body fixed. This point will be within a
        # pixel of the origin but in the same direction as the requested la/lon
        # of the point of interest, i.e. the subsolar point
        nB = spice.vadd(self.spoint, spoB)

        coords = SurfaceCoords.fromtuple(spice.reclat(nB))
        return coords.dlon, coords.dlat
Example #6
0
    def subsolar(self):
        # normalize surface point vector:
        uuB = spice.vhat(self.center_to_sun)

        # receive subsolar point in IAU_MARS rectangular coords
        # the *self.radii unpacks the Radii object into 3 arguments.
        v_subsolar = spice.surfpt((0, 0, 0), uuB, *self.radii)

        return v_subsolar
Example #7
0
    def _get_subsolar(self):
        # normalize surface point vector:
        uuB = spice.vhat(self.center_to_sun)

        # receive subsolar point in IAU_MARS rectangular coords
        # the *self.radii unpacks the Radii object into 3 arguments.
        v_subsolar = spice.surfpt((0, 0, 0), uuB, *self.radii)

        return v_subsolar
Example #8
0
def parse_inertial(input_vertex, return_radec=True):
  """Parse input vertex as either RA,Dec or XYZ;
Return RA,DEC (optional) and unit vector

"""
  if 2==len(input_vertex):
    ### Vertex has two items:  assume they are RA and Dec
    ra,dec = map(float,input_vertex)
    assert ra<=360.0 and ra>=0.0,'RA ({0}) is out of range [0,360)'.format(ra)
    assert dec<=90.0 and dec>=-90.0,'RA ({0}) is out of range [-90,+90]'.format(dec)
    uvxyz = sp.radrec(1.0,ra*rpd,dec*rpd)
  else:
    ### Vertex has three items:  assume they are XYZ
    assert 3==len(input_vertex),'XYZ input vector [{0}] for vertex does not have 3 elements'.format(str(input_vertex))
    uvxyz = sp.vhat(sp.vpack(*map(float,input_vertex)))
    if return_radec: ra,dec = sp.vsclg(dpr,sp.recrad(uvxyz)[1:],2)

  if return_radec: return ra,dec,uvxyz
  return uvxyz
Example #9
0
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
Example #10
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
Example #11
0
def dooneRandom():
    ### Random normal unit vector
    unitNormal = sp.vhat([r.uniform(-1, 1) for i in xrange(3)])

    ### Random positive semi-axes' lengths
    semi_axes = [abs(r.uniform(.5, 100)) for i in xrange(3)]

    ### Solve for surface point with that surface normal
    xyz = norm2surfpt(semi_axes, unitNormal)

    ### Use spiceypy.surfnm() to calculate surface unit normal vector, at
    ### solved-for surface point vector xyz, to compare to input unit normal
    ### vector, and calculate error magnitude, errMag

    surfnm = sp.surfnm(semi_axes[0], semi_axes[1], semi_axes[2], xyz)
    errVec = sp.vsub(surfnm, unitNormal)
    errMag = sp.vnorm(errVec)

    if doLog:
        pprint.pprint(dict(errVec=errVec, errMag=errMag))

    ###return error magnitude
    return errMag
Example #12
0
def caps_crosstrack_latlon(time, negwindspeed, poswindspeed, anodes=False):
    anode_vecs = []
    anode_seps = [[], [], [], [], [], [], [], []]
    beamanodes = []

    # print(time)
    beamanodes.append(np.mean(ELS_ramanodes(time)) + 1)
    state = cassini_phase(time.strftime('%Y-%m-%dT%H:%M:%S'))
    # crossvec = caps_crosstrack(time, windspeed) * abs(windspeed) * 1e-3 #Old Method
    crossvec, anode1, anode8 = caps_crosstrack_spice(
        time, np.mean([negwindspeed, poswindspeed]))  # SPICE Plane method
    crossvec_neg = crossvec * 1e-3
    crossvec_pos = crossvec * 1e-3

    print("crossvec", crossvec_neg, crossvec_pos)
    newstate_neg = list(state[:3]) + list(crossvec_neg)
    newstate_pos = list(state[:3]) + list(crossvec_pos)
    transformed_state_neg = spice.xfmsta(newstate_neg, 'RECTANGULAR',
                                         'Latitudinal', "TITAN")
    transformed_state_pos = spice.xfmsta(newstate_pos, 'RECTANGULAR',
                                         'Latitudinal', "TITAN")
    print("test state", newstate_neg, newstate_pos)
    print("test xfmsta", transformed_state_neg, transformed_state_pos)
    alt = transformed_state_neg[0]
    lon = transformed_state_neg[1] * spice.dpr()
    lat = transformed_state_neg[2] * spice.dpr()

    if anodes:
        anode_vecs.append(caps_all_anodes(time))
        for anodecounter, i in enumerate(anode_vecs[-1]):
            anode_seps[anodecounter].append(
                spice.vsep(spice.vhat(state[3:]),
                           spice.vhat(anode_vecs[-1][anodecounter])) *
                spice.dpr())
        print("anode_vecs", anode_vecs)
        print("anode_seps", anode_seps)
    dvec_neg = [
        transformed_state_neg[4], transformed_state_neg[5],
        transformed_state_neg[3]
    ]
    dvec_pos = [
        transformed_state_pos[4], transformed_state_pos[5],
        transformed_state_pos[3]
    ]
    print("dvec xfmsta", dvec_neg, dvec_pos)
    print(lon, lat, alt)

    titanrad = spice.bodvrd('TITAN', 'RADII', 3)[1][0]  # Get Titan Radius
    mag_test_neg = spice.unorm([
        dvec_neg[0] * (alt + titanrad) * 1e3,
        dvec_neg[1] * (alt + titanrad) * 1e3, dvec_neg[2] * 1e3
    ])
    mag_test_pos = spice.unorm([
        dvec_pos[0] * (alt + titanrad) * 1e3,
        dvec_pos[1] * (alt + titanrad) * 1e3, dvec_pos[2] * 1e3
    ])
    # Convert Lat/Lon from rad/s to m/s, convert Alt from km/s to m/s. Forms Unit Vector here
    print("mag test", mag_test_pos)

    dlon_neg = mag_test_neg[0][0] * abs(negwindspeed)
    dlat_neg = mag_test_neg[0][1] * abs(negwindspeed)
    dalt_neg = mag_test_neg[0][2] * abs(negwindspeed)

    dlon_pos = mag_test_pos[0][0] * abs(poswindspeed)
    dlat_pos = mag_test_pos[0][1] * abs(poswindspeed)
    dalt_pos = mag_test_pos[0][2] * abs(poswindspeed)

    return lon, lat, alt, dlon_neg, dlat_neg, dalt_neg, dlon_pos, dlat_pos, dalt_pos
Example #13
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()
Example #14
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
Example #15
0
def norm2surfpt(semi_axes, inputNormal):

    ### Ellipsoid semi-axes' lengths, per the ellipsoid formula:
    ###
    ###        2           2           2
    ###   / x \       / y \       / z \
    ###  ( --- )  +  ( --- )  +  ( --- ) =  1
    ###   \ a /       \ b /       \ c /

    ### Ensure abc components are positive
    abc = sp.vequ([abs(semi_axis) for semi_axis in semi_axes[:3]])

    ### Get unit vector, n, parallel to input normal
    n = sp.vhat(inputNormal)

    ### Direction of normal, N, at [x,y,z] is [ x/(a*a), y/(b*b), z/(c*c)]
    ### - Vector N is unknown, and is not necessarily a unit vector
    ### - Argument inputNormal is parallel to N, and of arbitrary length
    ### - Unit vector parallel to N is n, calculated above from inputNormal
    ### - Assume length of N is scalar 1/k; k is initially unknown
    ### - Scaling unit normal, n, by k yields N:
    ###
    ###     n/k = N = [x/(a*a), y/(b*b), z/(c*c)]      Eqn. 1
    ###
    ### so
    ###
    ###     nx/k = x/(a*a)                                    Eqn. 2x
    ###     ny/k = y/(b*b)                                    Eqn. 2y
    ###     nz/k = z/(c*c)                                    Eqn. 2z
    ###
    ### and, solving for surface point components, [x,y,z]:
    ###
    ###     x = nx*a*a/k                                      Eqn. 3x
    ###     y = ny*b*b/k                                      Eqn. 3y
    ###     z = nz*c*c/k                                      Eqn. 3z
    ###
    ### Substituting Eqns. 3x, 3y, and 3z for x, y, and z
    ### back into the ellipsoid formula (x^2/a^2 + ... = 1):
    ###
    ###     (nx*a*a/k)^2/(a*a)
    ###   + (ny*b*b/k)^2/(b*b)
    ###   + (nz*c*c/k)^2/(c*c) = 1                            Eqn. 4
    ###
    ### and solving for k:
    ###
    ###     (nx*a)^2 + (ny*b)^2 + (nz*c)^2 = k^2              Eqn. 5
    ###
    ### Since nx, a, ny, b, nz, and c are all known, k
    ### can be calculated directly using Eqn. 5, and the
    ### surface point components, x, y, and z, can then
    ### be calculated using Eqns. 3x, 3y, and 3z.

    ### Calculate two vectors:
    ### - [a*a, b*b, c*c]
    ### - [nx*nx, ny*ny, nz*nz]

    abcXabc = vXv(abc, abc)
    nXn = vXv(n, n)

    ### Solve for k using abcXabc, nXn, and Eqn. 5:

    k2 = sp.vdot(abcXabc, nXn)
    k = math.sqrt(k2)

    ### Use k, abcXabc, n, and Eqns. 3x, 3y, and
    ### 3z to calculate surface point vector xyz

    xyz = sp.vscl(1. / k, vXv(abcXabc, n))

    ### Debug logging:
    if doLog:
        ### Calculate (x/a)^2 + (y/b)^2 + (z/c)^2; it should be = 1
        one = sp.vdot(vXv(xyz, xyz), 1 / abcXabc)
        pprint.pprint(
            dict(n=n,
                 abc=abc,
                 abcXabc=abcXabc,
                 nXn=nXn,
                 nMag=sp.vnorm(n),
                 k=k,
                 k2=k2,
                 xyz=xyz,
                 one=one))

    ### Return surface point

    return xyz
Example #16
0
  for iet in range(-400,401):

    ### ET (TDB)
    et = float(iet) / 10
    ets.append(et)

    ### Position of Bennu (2101955) wrt instrument
    storxinstr,ltorxinstr = sp.spkezr('2101955',et,'J2000','none','-64999')
    storx,ltorx = sp.spkezr('2101955',et,'J2000','none','-64')

    ### Position of Bennu (2101955) wrt S/C COM
    porxinstr,vorxinstr = storxinstr[:3],storxinstr[3:]
    porx,vorx = storx[:3],storx[3:]

    ### Delta dRange/dt
    dvs.append( sp.vdot(vorxinstr,sp.vhat(porx))
              - sp.vdot(vorx,sp.vhat(porx))
              )

    porxinstrs.append(list(porxinstr))
    vorxinstrs.append(list(vorxinstr))
    porxs.append(list(porx))
    vorxs.append(list(vorx))

  with open('relvel.json','w') as fjson:
    sj.dump(dict(porxinstrs=porxinstrs
                ,vorxinstrs=vorxinstrs
                ,porxs=porxs
                ,vorxs=vorxs
                ,dvs=dvs
                ,ets=ets
    degPerHour = 2.0 * dpr * twopi / hpd

    xTolerance = lambda xDiff: xDiff < 1e-10
    xDiffTolerance = lambda x, xExpect: xTolerance(abs(x - xExpect))

    et, iPass = et0, 0
    while et < (et0 + spd + 1):

        ### Get the state of the MINUTE and HOUR bodies every half hour
        stMinute, lt = sp.spkezr(sMinute, et, 'J2000', 'NONE', sClock)
        stHour, lt = sp.spkezr(sHour, et, 'J2000', 'NONE', sClock)

        if (iPass % 2):
            ### On the half hour, the minute hand will be at RA=180, along [-1,0,0]
            assert xTolerance(
                sp.vnorm(sp.vsub([-1.0, 0., 0.], sp.vhat(stMinute[:3]))))
        else:
            ### On the hour, the minute hand will be at RA=0, at [+1,0,0]
            assert xTolerance(
                sp.vnorm(sp.vsub([1.0, 0., 0.], sp.vhat(stMinute[:3]))))
            vsepDeg = dpr * sp.vsep(stHour[:3], stMinute[:3])
            vsepExpectDeg = (degPerHour * (iPass >> 1)) % 360
            ### On the hour, the houre hand will be (30 * iPass) degrees clockwise
            ### from +X, and therefor also the same from the minute hand
            assert xDiffTolerance(vsepDeg, vsepExpectDeg) or xDiffTolerance(
                (360 - vsepDeg), vsepExpectDeg)

        ### Step to next half hour and pass
        et += halfHour
        iPass += 1
Example #18
0
  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
                           "earth")[0]

if do_debug:
    ### Ensure that light-time correction is zero for earth->star vector
    ### for star at fixed positon relative to SSB
    earth2star_lt = sp.spkcpt(vstar, ssb, j2000, et, j2000, "observer", LT,
                              "earth")[0]
    assert 0.0 == sp.vnormg(sp.vsubg(earth2star_none, earth2star_lt, 6), 6)

### Calculate differences between gaiif_util.py and SPICE results ...
ab_vec_spice = sp.vsub(earth2star_lts[:3], earth2star_none[:3])
ab_vec_gaia = sp.vscl(range2star, sp.vsub(uvstar_ab, uvstar_noab))
ab_vec_diff = sp.vsub(ab_vec_spice, ab_vec_gaia)
ab_diff_frac = sp.vnorm(ab_vec_diff) / sp.vnorm(ab_vec_spice)

no_ab_diff = sp.vsep(uvstar_noab, sp.vhat(earth2star_none[:3]))
ab_diff = sp.vsep(uvstar_ab, sp.vhat(earth2star_lts[:3]))
ab_diff_frac = sp.vsep(uvstar_ab, sp.vhat(earth2star_lts[:3]))

try:
    ### ... Success
    assert 1e-15 > no_ab_diff
    assert 1e-8 > ab_diff
except:
    ### ... Failure
    print(
        dict(no_ab_diff=no_ab_diff,
             ab_diff=ab_diff,
             ab_diff_frac=ab_diff_frac,
             ab_mag_spice=sp.vsep(earth2star_none[:3], earth2star_lts[:3]),
             ab_mag_gaia=sp.vsep(uvstar_noab, uvstar_ab)))
Example #20
0
  def star_in_fov(self
                 ,vstar
                 ,parallax_maspau=None
                 ,pmra_maspy=None
                 ,pmdec_maspy=None
                 ):
    """Return True if the star (RA,Dec or xyz) argument is in the FOV

Argument vstar is either an RA,Dec pair (degrees) or a 3-vector

"""

    if self.fovtype == FOV.RADECBOXTYPE:
      ##################################################################
      ### Compare star vector to [RA,Dec] box
      ra,dec = parse_inertial(vstar,return_radec=True)[:2]
      for ralo,rahi,declo,dechi in self.radec_boxes:
        if ra<ralo: continue
        if ra>rahi: continue
        if dec<declo: continue
        if dec>dechi: continue
        return True,sp.radrec(1.,rpd*ra,rpd*dec)
      return False,None

    ### Get inertial star unit vector without RA,Dec
    uvinertial = uvraw = parse_inertial(vstar,return_radec=False)

    ### Corrections for direction to star
    ### - Assume all corrections are small and can be applied in units
    ###   of radians to a unit vector in a plane perpendicular to the
    ###   vector to the star

    ### - Proper Motion (PM)
    ###   - Uses PM in RA and Dec only, not radial velocity and parallax
    if (not (None is self.obs_year)
       ) and (not (None in (pmra_maspy,pmdec_maspy,))
       ) and (pmra_maspy != 0.0 or pmdec_maspy != 0.0):
      ### - Unit vectors E and N in plane perpendicular to star vector
      uveast = sp.ucrss([0,0,1],uvraw)
      uvnorth = sp.ucrss(uvraw,uveast)
      ### - Scale unit vectors in radians, and add to nominal vector
      ### - pmra_maspy from Gaia includes factor of secant(Declination)
      uvinertial = sp.vhat(sp.vlcom3(self.obs_year*rpmas*pmdec_maspy,uvnorth
                                    ,self.obs_year*rpmas*pmra_maspy,uveast
                                    ,1.0,uvinertial
                                    )
                          )

    ### - Parallax
    if (not (None is self.obs_pos)
       ) and (not (None is parallax_maspau)
       ) and (parallax_maspau != 0.0):
      ### - Scale observer position, by parallax in mas/AU, then scale
      ###   to radians (since star vector is unit vector), make that the
      ###   new origin of the vector
      uvinertial = sp.vhat(sp.vsub(uvinertial
                                  ,sp.vscl(aupkm*parallax_maspau*rpmas,self.obs_pos)
                                  )
                          )

    ### - Stellar Aberration
    if not (None is self.obs_vel):
      ### - Scale observer velocity by reciprocal of the speed of light,
      ###   add result to unit vector toward star.
      uvinertial = sp.vhat(sp.vadd(uvinertial
                                  ,sp.vscl(recip_clight,self.obs_vel)
                                  )
                          )

    if self.fovtype == FOV.CIRCLETYPE:
      ##################################################################
      ### Compare inertial star vector to circular FOV
      return sp.vdot(uvinertial,self.uv_cone_axis) >= self.min_cosine,uvinertial

    assert FOV.POLYGONTYPE == self.fovtype,'Unknown FOV type [{0}]'.format(self.fovtype)

    ####################################################################
    ### Compare star vector to polygonal FOV

    if self.is_convex():
      ### Convex FOV:  a negative dot product with the inward-pointing
      ###              normal to any side indicates star is outside FOV
      for inwardnorm in self.inwardsidenorms:
        if sp.vdot(uvinertial,inwardnorm) < 0.0: return False,uvinertial

      ### All dot products were non-negative:  star is within FOV
      return True,uvinertial

    ### Rotate inertial unit vector to local reference frame (reffrm)
    uvlocalstar = self.rotate_to_local(uvinertial)

    ### Scale to Z=unity
    if uvlocalstar[2] < 1e-15: return False,uvinertial
    z1star = sp.vscl(1.0/uvlocalstar[2],uvlocalstar)

    ### Setup .localxyzs and .fovsides
    if None is self.localxyzs: self.setup_localxyzs()

    ### Count number of crossings of FOV sides
    count = len([None
                 for fovside in self.fovsides
                 if fovside.right_of(z1star)
                ])

    return ((count&1) and True or False),uvinertial