def producegeometrylamda(et, sv, when):
    [TGO, _] = spice.spkpos(sv.front, et - when, sv.fframe, 'NONE', sv.target)
    [MEX, _] = spice.spkpos(sv.front, et - when, sv.fframe, 'NONE', sv.obs)

    dist = math.floor(spice.vdist(TGO, MEX))
    print(dist)
    # NEED TO PRODUCE VECTOR OF SZA AND HEIGHTS THAT ARE 'DIST' LONG [13242.9 m] *comp expensive
    # start by making DIST length vector, 3 height, for every meter from mex to tgo
    # MAYBE FIND THE UNIT VECTOR AND ADD ONE IN ITS DIRECTION!!
    angleseparation = (spice.vsep(MEX, TGO)) * (180 / math.pi
                                                )  # angle taken a mars center
    initialangle = (spice.vsep(-MEX, (TGO - MEX))) * (
        180 / math.pi
    )  # angle taken at mars-MEX-tgo, that points to tgo. needed for the bending functions original starting angle
    #script needs to work via periods of ray and not meters. [totalperiods is the main iterable, not meters]
    vacuumwavelength = constants.c / 437.1e6
    scale = 1  # scale =10, means we are itertating per 100 wavelenghts instead of 1000 (default 1000 because SPICE works in km)
    wavelengthsinameter = 1 / vacuumwavelength
    a = wavelengthsinameter * dist * scale
    total1000periods = math.floor(a)  # ~that many thousands of periods
    remainingdistance = (vacuumwavelength / scale) * (
        (wavelengthsinameter * dist * scale) - total1000periods
    )  # quanitfy the remaineder, this distance can
    # added later, this remaining portion is extreamly high altitude (near Target) and has no refractive effects. therfor simply added (km)
    #total1000periods = total1000periods.astype(int)

    sc2sc = TGO - MEX
    norm = np.linalg.norm(sc2sc)
    unitsc2sc = sc2sc / (norm * vacuumwavelength * scale
                         )  #this needs to shrink if the repeatable expands
    points = np.empty([3, total1000periods])
    sza = np.empty([1, total1000periods])

    marsrad = spice.bodvrd(sv.front, 'RADII', 3)
    flatteningcoefficient = (marsrad[1][0] - marsrad[1][2]) / marsrad[1][0]
    equatorialradii = marsrad[1][0]
    # find direction of sun, it will not change much during the occultation. so only calc it once
    [SUN, _] = spice.spkpos(sv.front, et, sv.fframe, 'NONE', 'SUN')
    for i in range(total1000periods):
        point = MEX + (
            i * unitsc2sc
        )  #move along ray, 1000 wavelength distance at a time (685 m). but unitsc2sc is in km...
        sza[0, i] = spice.vsep(SUN, point)
        points[:, i] = spice.recgeo(point, equatorialradii,
                                    flatteningcoefficient)
        points[0, i] = (points[0, i] * (-180 / math.pi))
        points[1, i] = (points[1, i] * (-180 / math.pi))

        print((i / math.floor(total1000periods)) * 100)

    ray = np.concatenate((points, sza), axis=0)
    print('stop here')

    return ray, dist, angleseparation, initialangle, total1000periods, vacuumwavelength, remainingdistance
def producegeometrymeter(et, sv, when):
    [TGO, _] = spice.spkpos(sv.front, et - when, sv.fframe, 'NONE', sv.target)
    [MEX, _] = spice.spkpos(sv.front, et - when, sv.fframe, 'NONE', sv.obs)

    dist = math.floor(spice.vdist(TGO, MEX))
    print(dist)
    # NEED TO PRODUCE VECTOR OF SZA AND HEIGHTS THAT ARE 'DIST' LONG [13242.9 m] *comp expensive
    # start by making DIST length vector, 3 height, for every meter from mex to tgo
    # MAYBE FIND THE UNIT VECTOR AND ADD ONE IN ITS DIRECTION!!
    angleseparation = (spice.vsep(MEX, TGO))  # angle taken a mars center
    initialangle = (spice.vsep(-MEX, (TGO - MEX))) * (
        180 / math.pi
    )  # angle taken at mars-MEX-tgo, that points to tgo. needed for the bending functions original starting angle
    #script needs to work via periods of ray and not meters. [totalperiods is the main iterable, not meters]

    scale = 0.1  # scale =10, means we are itertating per 100 wavelenghts instead of 1000 (default 1000 because SPICE works in km)

    dist = math.floor(dist)  # km

    sc2sc = TGO - MEX
    norm = np.linalg.norm(sc2sc)
    unitsc2sc = sc2sc / norm  #this needs to shrink if the repeatable expands
    points = np.empty([3, dist])
    sza = np.empty([1, dist])
    angleprogression = np.empty([1, dist])

    xyzpoints = np.zeros([3, dist])
    marsrad = spice.bodvrd(sv.front, 'RADII', 3)
    flatteningcoefficient = (marsrad[1][0] - marsrad[1][2]) / marsrad[1][0]
    equatorialradii = marsrad[1][0]
    # find direction of sun, it will not change much during the occultation. so only calc it once
    [SUN, _] = spice.spkpos(sv.front, et, sv.fframe, 'NONE', 'SUN')
    for i in range(dist):
        xyzpoint = MEX + (
            i * unitsc2sc
        )  #move along ray, 1000 wavelength distance at a time (685 m). but unitsc2sc is in km...
        xyzpoints[:, i] = xyzpoint
        sza[0, i] = spice.vsep(SUN, xyzpoint)
        angleprogression[0, i] = (spice.vsep(xyzpoint, MEX)) * (180 / math.pi)
        points[:, i] = spice.recgeo(xyzpoint, equatorialradii,
                                    flatteningcoefficient)
        points[0, i] = (points[0, i] * (-180 / math.pi))
        points[1, i] = (points[1, i] * (-180 / math.pi))

        print((i / math.floor(dist)) * 100)

    ray = np.concatenate((points, sza), axis=0)

    #plt.plot(angleprogression[0,:], ray[2,:])
    #plt.show()

    # ray is in lat/lon/alt + sza and xyzpoints is cartesian, both describe the same thing
    return ray, dist, unitsc2sc, angleseparation, initialangle, MEX, TGO, xyzpoints, angleprogression
Example #3
0
 def _calc_fovmax(self):
     fovmax = -1.0
     if self._shape in ["RECTANGLE", "POLYGON"]:
         fovmax = -1.0
         for i, j in combinations(range(len(self._bounds)), 2):
             dist = spice.vsep(self._bounds[i], self._bounds[j]) * 0.5
             if dist > fovmax:
                 fovmax = dist
     elif self._shape == "CIRCLE":
         fovmax = spice.vsep(self._boresight, self._bounds[0])
     elif self._shape == "ELLIPSE":
         dist1 = spice.vsep(self._boresight, self._bounds[0])
         dist2 = spice.vsep(self._boresight, self._bounds[1])
         fovmax = dist1 if dist1 > dist2 else dist1
     return fovmax
Example #4
0
def printBoresights(angleSeparationA, angleSeparationB):
    """input manual rotation angles from SPICE kernels to calculate new and old boresight"""
    oldSoBoresight = [0.0, 0.0, 1.0]
    oldUVISBoresight = [0.0, 0.0, 1.0]
    rotationMatrixSoUVIS = sp.pxform("TGO_NOMAD_SO", "TGO_NOMAD_UVIS_OCC",
                                     sp.utc2et("2018 APR 01 00:00:00 UTC"))
    oldSoBoresightUVIS = np.dot(oldSoBoresight, rotationMatrixSoUVIS.T)
    oldBoresightSeparation = sp.vsep(oldUVISBoresight,
                                     oldSoBoresightUVIS) * sp.dpr() * 60.0
    print("oldBoresightSeparation")
    print(oldBoresightSeparation)

    print("angleSeparationB")
    print(angleSeparationB)
    #####SAVE THIS IT WORKS!!!######
    newSoBoresightTGO = np.asfarray([
            -1.0 * np.sin(angleSeparationB / sp.dpr()), \
            np.sin(angleSeparationA / sp.dpr()) * np.cos(angleSeparationB / sp.dpr()), \
            np.cos(angleSeparationA / sp.dpr()) * np.cos(angleSeparationB / sp.dpr())])

    print("newSoBoresightTGO, vnorm = %0.6f" % sp.vnorm(newSoBoresightTGO))
    print(newSoBoresightTGO)

    newUVISBoresightTGO = np.asfarray(
        [-0.922221097920913, -0.386613383297695, 0.006207330031467])
    oldSoBoresightTGO = np.asfarray([-0.92156, -0.38819, 0.00618])
    oldUVISBoresightTGO = np.asfarray(
        [-0.92207347097, -0.3869614566418, 0.0064300242046])

    oldNewSoBoresightSeparation = sp.vsep(newSoBoresightTGO,
                                          oldSoBoresightTGO) * sp.dpr() * 60.0
    print("oldNewSoBoresightSeparation")
    print(oldNewSoBoresightSeparation)

    oldNewUVISBoresightSeparation = sp.vsep(
        newUVISBoresightTGO, oldUVISBoresightTGO) * sp.dpr() * 60.0
    print("oldNewUVISBoresightSeparation")
    print(oldNewUVISBoresightSeparation)

    newSoUVISBoresightSeparation = sp.vsep(
        newSoBoresightTGO, newUVISBoresightTGO) * sp.dpr() * 60.0
    print("newSoUVISBoresightSeparation")
    print(newSoUVISBoresightSeparation)

    oldSoUVISBoresightSeparation = sp.vsep(
        oldSoBoresightTGO, oldUVISBoresightTGO) * sp.dpr() * 60.0
    print("oldSoUVISBoresightSeparation")
    print(oldSoUVISBoresightSeparation)
def Location(et, ingress, sv, when):
    Coords = np.ones(3)
    [tgopos, _] = spice.spkpos(sv.front, et - when, sv.fframe, 'NONE',
                               sv.target)
    [mexpos, _] = spice.spkpos(sv.front, et - when, sv.fframe, 'NONE', sv.obs)
    [states, _] = spice.spkezr(sv.target, et - when, sv.fframe, 'NONE', sv.obs)
    sc2scvector = states[0:3]
    velocity = states[3:6]
    relativespeed = np.linalg.norm(velocity)
    # e9 because we are converting from km to m (SPICE outputs km, but constants in m)
    veldopp = (relativespeed / constants.c) * 437.1e9
    displacement = np.linalg.norm(sc2scvector)
    sc2scunitvector = np.true_divide(sc2scvector, displacement)
    # Extract the triaxial dimensions of Mars
    marsrad = spice.bodvrd(sv.front, 'RADII', 3)
    # For the ray that connects MEX and TGO, find the point on this ray that is closest to the Martian surface
    [nearestpoint, alt] = spice.npedln(marsrad[1][0], marsrad[1][1],
                                       marsrad[1][2], tgopos, sc2scunitvector)
    # THERE IS MORE SETTINGS ON THIS
    [radius, lon, lat] = spice.reclat(nearestpoint)
    # Rad -> Deg , frame inversion required (hence the negative 180)
    lon = 180 - (lon * (-180 / math.pi))
    lat = lat * (-180 / math.pi)

    MexNadirTGOAngle = spice.vsep(-mexpos, -sc2scvector)
    MexNadirTGOAngle = MexNadirTGOAngle * (180 / math.pi)

    # produce a string of the date and time, because an ephemeris time is not human-readable
    date_time = spice.timout(et, 'MM-DD HR:MN:SC')
    ingress_date_time = spice.timout(ingress, 'MM-DD HR:MN:SC')
    return lon, lat, displacement, nearestpoint, alt, relativespeed, date_time, ingress_date_time, veldopp, MexNadirTGOAngle
def producegeometrymeter(et, sv, when):

    [TGO, _] = spice.spkpos(sv.front, et - when, sv.fframe, 'NONE', sv.target)
    [MEX, _] = spice.spkpos(sv.front, et - when, sv.fframe, 'NONE', sv.obs)

    dist = math.floor(spice.vdist(TGO, MEX))

    angleseparation = (spice.vsep(MEX, TGO))  # angle taken a mars center
    initialangle = (spice.vsep(-MEX, (TGO - MEX))) * (
        180 / math.pi
    )  # angle taken at mars-MEX-tgo, that points to tgo. needed for the bending functions original starting angle
    #script needs to work via periods of ray and not meters. [totalperiods is the main iterable, not meters]

    sc2sc = TGO - MEX
    norm = np.linalg.norm(sc2sc)
    unitsc2sc = sc2sc / norm  #this needs to shrink if the repeatable expands
    points = np.empty([3, dist])
    sza = np.empty([1, dist])
    angleprogression = np.empty([1, dist])

    xyzpoints = np.zeros([3, dist])
    marsrad = spice.bodvrd(sv.front, 'RADII', 3)
    flatteningcoefficient = (marsrad[1][0] - marsrad[1][2]) / marsrad[1][0]
    equatorialradii = marsrad[1][0]
    # find direction of sun, it will not change much during the occultation. so only calc it once
    [SUN, _] = spice.spkpos(sv.front, et, sv.fframe, 'NONE', 'SUN')
    for i in range(dist):
        xyzpoint = MEX + (
            i * unitsc2sc
        )  #move along ray, 1000 wavelength distance at a time (685 m). but unitsc2sc is in km...
        xyzpoints[:, i] = xyzpoint
        sza[0, i] = spice.vsep(SUN, xyzpoint)
        angleprogression[0, i] = (spice.vsep(xyzpoint, MEX)) * (180 / math.pi)
        points[:, i] = spice.recgeo(xyzpoint, equatorialradii,
                                    flatteningcoefficient)
        points[0, i] = (points[0, i] * (-180 / math.pi))
        points[1, i] = (points[1, i] * (-180 / math.pi))

    ray = np.concatenate((points, sza),
                         axis=0)  # important for when sza is included

    #plt.plot(angleprogression[0,:], ray[2,:])
    #plt.show()

    # ray is in lat/lon/alt + sza and xyzpoints is cartesian, both describe the same thing
    return initialangle, MEX, TGO, xyzpoints
Example #7
0
 def _get_flux(self, vector):
     diff_angle = spice.vsep(vector, self.sun_direction)
     if (self.illum_angles.dsolar > 90 * u.deg) or (np.degrees(diff_angle) >
                                                    90):
         return 0 * u.W / (u.m * u.m)
     else:
         return (self.solar_constant * np.cos(diff_angle) *
                 np.exp(-self.tau / np.cos(self.illum_angles.solar)))
Example #8
0
 def _get_illum_angles(self):
     "Ilumin returns (trgepoch, srfvec, phase, solar, emission)"
     if self.obs is not None:
         output = spice.ilumin("Ellipsoid", self.target, self.et, self.ref_frame, self.corr, self.obs, self.spoint)
         return IllumAngles.fromtuple(output[2:])
     else:
         solar = spice.vsep(self.sun_direction, self.snormal)
         # leaving at 0 what I don't have
         return IllumAngles.fromtuple((0, solar, 0))
def SolarZenithAngles(et, nearestpoint, sv):

    subsolarpoint, _, _ = spice.subslr('INTERCEPT/ELLIPSOID', sv.front, et,
                                       sv.fframe, 'NONE',
                                       sv.target)  # Where is the sun?
    sza = spice.vsep(subsolarpoint, nearestpoint)  # angle between sun and sc
    latersubsolarpoint, _, _ = spice.subslr(
        'INTERCEPT/ELLIPSOID', sv.front, et + 30, sv.fframe, 'NONE',
        sv.target)  # where is the sun later?
    latersza = spice.vsep(latersubsolarpoint, nearestpoint)

    if sza < latersza:  #if the sun is moving away from the tangent point (PM)
        sza = sza * (180 / math.pi)
        sza = sza * (-1)  # negative SZA mean evening

    sza = sza * (180 / math.pi)  # positive SZA means mornings

    return sza
def earlyclearance(et, sv):
    [Mars_TGO, _] = spice.spkpos(sv.front, et - 60, sv.fframe, 'NONE',
                                 sv.target)
    [Mars_MEX, _] = spice.spkpos(sv.front, et - 60, sv.fframe, 'NONE', sv.obs)
    [spiceTGO_MEX, _] = spice.spkpos(sv.target, et - 60, sv.fframe, 'NONE',
                                     sv.obs)

    #TGO_MEX = Mars_TGO - Mars_MEX
    #testangle = spice.vsep(spiceTGO_MEX,TGO_MEX) * (180/math.pi)
    # We use a negative Mars_TGO vector because we want the opposite direction(going to Mars)
    clearanceangle = spice.vsep(-Mars_TGO, spiceTGO_MEX) * (180 / math.pi)
    return clearanceangle
Example #11
0
def findBoresightUsed(et, boresight_vectors, boresight_names):

    obs2SunVector = sp.spkpos("SUN", et, SPICE_REFERENCE_FRAME, SPICE_ABERRATION_CORRECTION, SPICE_OBSERVER)
    
    v_norm = obs2SunVector[0]/sp.vnorm(obs2SunVector[0])
    
    v_sep_min = 999.0
    for boresight_vector, boresight_name in zip(boresight_vectors, boresight_names):
        v_sep = sp.vsep(v_norm, boresight_vector) * sp.dpr() * 60.0 #arcmins difference
        if v_sep < v_sep_min:
            v_sep_min = v_sep
            boresight_found = boresight_name
    return boresight_found, v_sep_min
Example #12
0
    def __init__(self, inst_id):
        fov = spice.getfov(inst_id, Fov.MAX_ROOMS)
        self._shape = fov[0]
        self._frame = fov[1]
        self._boresight = fov[2]
        self._nbounds = fov[3]
        self._bounds = fov[4]

        self._bounds_rect = self._calc_bounds_rect()
        self._fovmax = self._calc_fovmax()
        self._fovy = (
            spice.vsep(self.bounds_rect.center_vec, self.bounds_rect.top_vec) *
            2.0 * spice.dpr())
        self._aspect = self.bounds_rect.aspect
Example #13
0
 def illum_angles(self):
     """Ilumin returns (trgepoch, srfvec, phase, solar, emission)
     """
     if self.obs is not None:
         output = spice.ilumin(
             "Ellipsoid",
             self.target,
             self.et,
             self.ref_frame,
             self.corr,
             self.obs,
             self.spoint,
         )
         return IllumAngles.fromtuple(output[2:])
     else:
         solar = spice.vsep(self.sun_direction, self.snormal)
         # leaving at 0 what I don't have
         return IllumAngles.fromtuple((0, solar, 0))
def grazingangle(et, sv):

    # find the shape of the occultation profile TERRIBLY SLOW
    Profile, _, ht = occgradient(sv.front, et, sv.fframe, sv.obs, sv.target)
    x, y, z = Profile[0], Profile[1], Profile[2]
    lowestpoint = np.asarray([x[0], y[0], z[0]])
    highestpoint = np.asarray([x[ht - 1], y[ht - 1], z[ht - 1]])
    # find the vector from start to end of profile
    maxsc2scvector = (highestpoint - lowestpoint)
    norm = np.linalg.norm(maxsc2scvector)
    endunitvector = maxsc2scvector / norm
    norm = np.linalg.norm(lowestpoint)
    # provide unit vector for the lowestpoint, prepresenting the direction from Mars' center to the begining point
    startunitvector = lowestpoint / norm
    # producing the anlge between Mars' center ->start & start-> end
    angle = spice.vsep(startunitvector, endunitvector)
    angle = angle * (180 / math.pi)

    return angle
Example #15
0
def main():
    mspicer = MarsSpicer()
    mspicer.set_spoint_by(lat=85, lon=0)
    print("Set mspicer to 85N, 0E.")
    print("Local soltime: {0}".format(mspicer.local_soltime[3]))
    print("L_s: {0}".format(mspicer.l_s))
    print("Incidence angle: {0:g}".format(mspicer.illum_angles.dsolar))
    print("F_flat: {0:g}".format(mspicer.F_flat))
    mspicer.tilt = 30
    mspicer.aspect = 180
    print("F_tilt: {0:g}".format(mspicer.F_tilt))
    delta_in_rad = spice.vsep(mspicer.tilted_rotated_normal, mspicer.sun_direction)
    print("Angle between trnormal and sun: {0}".format(np.degrees(delta_in_rad)))
    print("F_aspect: {0:g}".format(mspicer.F_aspect))
    l_s, energies = mspicer.time_series("F_flat", 3600, no_of_steps=100, provide_times="l_s")
    energies_aspect = mspicer.time_series("F_aspect", 3600, no_of_steps=100)
    plt.plot(l_s, energies, label="flat", linewidth=2)
    plt.plot(l_s, energies_aspect, label="aspect: 180", linewidth=2)
    plt.legend()
    plt.show()
Example #16
0
def get_body_angular_diameter_rad(probe: str, body: str,
                                  time: datetime) -> float:
    """ Calculates angular diameter of given body as viewed from probe at given time

    :param probe: SPICE name of probe
    :param body: SPICE name of target body
    :param time: datetime of computation
    :return: Angular diameter of body in radians
    """
    et = datetime2et(time)
    limb_points = spy.limbpt("TANGENT/ELLIPSOID", body, et, f"IAU_{body}",
                             "LT+S", "CENTER", probe, (0.0, 0.0, 1.0), np.pi,
                             2, 1.0, 1.0, 10)
    # output sanity check
    if any([npts != 1 for npts in limb_points[0]]):
        raise RuntimeError(
            "Unable to determine limb vectors for determining angular size of target."
        )
    limb_vectors = limb_points[3]
    return spy.vsep(*limb_vectors)
def doppler(residual, totalperiods, dist, remaining):
    # I FEEL BETTER ABOUT THIS METHOD, NEED TO CHECK IF THEY ARE RELATIVE THO AND SEE IF THE TRIG CHANGES THE VALUE AT ALL @ DEBUGGING
    time = np.zeros(600)
    for time in range(600, 0, -1):
        sc2scstates = spice.spkezr(sv.target, (657605289.1825405 - time),
                                   sv.fframe, 'LT+S', sv.obs)
        velocityvector = sc2scstates[0][3:6]
        velocityvector = velocityvector[0:3]
        positionalvector = sc2scstates[0][0:3]
        positionalvector = positionalvector[0:3]
        velocityangle = spice.vsep(positionalvector, velocityvector)  # rads
        relativevelocity = np.linalg.norm(velocityvector) * np.cos(
            velocityangle)

        geometricdopplershift[time] = -(relativevelocity /
                                        constants.c) * 437.1e6

    velocitydoppler[time] = geometricdopplershift * \
        1000  # becuase spice is in km and c is in m

    transmitfrequency = 437.1e6
    # dist needs to be altered, how many wavelenghts fit into the whole dist, that needs to be the iterable. wavelength is 61 cm. this is too small
    scale = 1  # scale =10, means we are itertating per 100 wavelenghts instead of 1000
    vacuumwavelength = (constants.c / transmitfrequency) / scale
    # to test if this is the rounding error in the geometricdistasnce
    total1000periods = ((1 / vacuumwavelength) * dist * scale)
    wavelength = np.ones([1, totalperiods])

    for i in range(totalperiods):
        wavelength[0, i] = (
            vacuumwavelength) * residual[0, i]  # resisdual is only dist long

    electricdistance = np.sum(wavelength)
    # now this has not got a rounding error
    geometricdistance = (vacuumwavelength * total1000periods) / scale
    delta = (electricdistance - geometricdistance + remaining)

    dopplershift = ((delta / geometricdistance) * transmitfrequency)
    return electricdistance, geometricdistance, dopplershift
def expectedpower(et, sv):
    [states, _] = spice.spkezr(sv.target, et, sv.fframe, 'NONE', sv.obs)
    [tgopos, _] = spice.spkpos(sv.front, et, sv.fframe, 'NONE', sv.target)
    sc2scvector = states[0:3]
    displacement = np.linalg.norm(sc2scvector)

    Rxangle = (spice.vsep(sc2scvector, -tgopos)) * (180 / math.pi)

    if Rxangle > 90:
        Rx = nan
        return

    angle = np.linspace(0, 90, 19)
    mingain = [
        6.2, 6, 5.7, 5.4, 4.8, 4, 3.2, 2.5, 1.8, 1.2, 0.7, 0.2, -0.3, -1.3,
        -2.6, -4, -5.4, -7, -8.6
    ]  # example Rx field pattern on electra
    f = interp1d(angle, mingain)
    newangles = np.linspace(0, 90, 1)
    interpolatedgain = f(newangles)
    AngleLoss = f(np.floor(Rxangle))

    # plt.plot(interpolatedgain)
    # plt.show

    transmitfrequency = 437.1e6
    vacuumwavelength = (constants.c / transmitfrequency)

    # FREE SPACE PATH LOSS
    # equivelent of 1/(x^2)
    DisplacementLoss = 20 * \
        log10((2 * math.pi * displacement*1000)/vacuumwavelength)

    Tx = 13  # MEX MELACOM transmit power
    Rx = Tx + DisplacementLoss + AngleLoss
    Rx + 30  # convert dBW -> dBm
    return Rx
    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

    print('SP-Kernel passed {} half-hour tests'.format(iPass))

    ### End of test
    ######################################################################
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
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)
Example #22
0
                      mars2tgo)[3] * sp.dpr()
            for time, mars2tgo in zip(list(times), list(mars2tgo_pos))
        ])

        print("Calculating altitudes")
        #calculate tangent point altitude
        altitudes = np.asfarray([
            sp.npedln(mars_axes[0], mars_axes[1], mars_axes[2], line_point,
                      line_vector)[1] for line_point, line_vector in zip(
                          list(line_points), list(line_vectors))
        ])

        print("Checking if occultations real or not")
        #check that tgo is really behind mars as seen from sun: calculate angle between tgo-sun vector and tgo-mars centre vector
        sep_angles = np.asfarray([
            sp.vsep(sp.vsub(line_coord, line_point), line_vector)
            for line_coord, line_point, line_vector in zip(
                list(tangent_coords), list(line_points), list(line_vectors))
        ])
        #if angle greater than 90 then tgo is between mars and sun, not behind mars
        valid_occ_indices = np.asfarray([
            1 if sep_angle <= sp.halfpi() else 0
            for sep_angle in list(sep_angles)
        ])

        print("Removing invalid points")
        #calculate all valid tangent altitudes
        tangent_altitudes = np.asfarray([
            altitude if valid_occ == 1 else np.nan
            for altitude, valid_occ in zip(list(altitudes),
                                           list(valid_occ_indices))
hdulist = fits.open(file)

im = hdulist['PRIMARY'].data
w = WCS(file)                  # Look up the WCS coordinates for this frame
center  = w.wcs.crval  # degrees

et = hdulist['PRIMARY'].header['SPCSCET']

file_tm = '/Users/throop/gv/dev/gv_kernels_new_horizons.txt'

sp.furnsh(file_tm)

(st_sun,lt) = sp.spkezr('Sun', et, 'J2000', 'LT', 'New Horizons')
(st_pluto,lt) = sp.spkezr('Pluto', et, 'J2000', 'LT', 'New Horizons')

ang_sep = sp.vsep(st_sun[0:3], st_pluto[0:3])
ang_phase_deg = (math.pi - ang_sep)*hbt.r2d

# Generate the pickle filename

file_stars_pkl = dir_out + sequence + '_stars_deg' + repr(radius) + '.pkl'

#==============================================================================
# Get stars from a star catalog
#==============================================================================

try:
    lun = open(file_stars_pkl, 'rb')
    cat = pickle.load(lun)
    lun.close()
    print("Loaded: " + file_stars_pkl)
Example #24
0
 def _get_flux(self, vector):
     diff_angle = spice.vsep(vector, self.sun_direction)
     if (self.illum_angles.dsolar > 90) or (np.degrees(diff_angle) > 90):
         return 0
     else:
         return self.solar_constant * math.cos(diff_angle) * math.exp(-self.tau / math.cos(self.illum_angles.solar))
def cartesianbending(xyzpoints, initialangle, sv, MEX, TGO):
    #form a coordinate system where tgo is @ y=0 and x= (5000 +norm), Mar's Barrycenter being @ [5000,0]

    #initialise non-global variables
    miniray = np.zeros(10)
    raystep = np.zeros(
        (2, 10000))  # create a large array to populate and then shrink later

    barry2mex = np.linalg.norm(MEX)
    barry2tgo = np.linalg.norm(TGO)

    #find the martian geomoerty so you can reliably find the altitude of a point
    marsrad = spice.bodvrd(sv.front, 'RADII', 3)
    flatteningcoefficient = (marsrad[1][0] - marsrad[1][2]) / marsrad[1][0]
    equatorialradii = marsrad[1][0]

    _, _, MEXalt = spice.recgeo(MEX, equatorialradii, flatteningcoefficient)
    _, _, TGOalt = spice.recgeo(TGO, equatorialradii, flatteningcoefficient)

    #the possition of MEX is found by assuming that it will be somewhere over the relative horizon from TGO
    # (meaning over θ = 90°), finding the angle between the MEX and TGO's negative vector, will give the coords of MEX
    MexRelativeElevation = spice.vsep(-TGO, MEX)  #radians
    mex_y = barry2mex * np.sin(MexRelativeElevation)
    mex_x = barry2mex * np.cos(MexRelativeElevation)

    mex = np.array([0 - mex_x, mex_y])
    tgo = np.array([0 + barry2tgo, 0])
    barry = np.array([0, 0])

    #to plot the non-refracted propogation, we must convert the 3d xyzpoints to 2d, we do this the same way we found the x&y for MEX
    # ,using the norm distance and sep from -TGO5
    length = np.size(xyzpoints, 1)
    UnrefractedDistance = np.linalg.norm(xyzpoints[:, 0] -
                                         xyzpoints[:, -1])  #in km
    UnrefractedRay = np.zeros([2, length])
    for i in range(length):
        point = xyzpoints[:,
                          i] + 0  #need to put vector into temp variable as spice cant handle strided array inputs
        angle = spice.vsep(-TGO, point)
        norm = np.linalg.norm(point)
        point_x = norm * np.cos(angle)
        point_y = norm * np.sin(angle)
        UnrefractedRay[0, i] = 0 - point_x
        UnrefractedRay[1, i] = point_y

    #psuedo
    #.decide what the unit is (probably iterable),start propergation with the initial angle in one unit direction (doesnt have to be 1 km),
    #. feed each unit/10 into an alt finder and calc the avg N for this unit
    #. simple snell to find new angle with n0/n1 (does it need entry angle?)
    #.find barry2ray possition, and the bend will be from the normal of that (simple)
    #.rotate current vector up(iono) or down(neutral) minutely
    #.save coords at each unit step
    # . propergate
    #.exit when y = ~0 (bare in mind the nuetral and really f**k up at low alts), this should show in a plot anyways
    #. check variance with an altered ploterrorvariation() function
    #.calc miss, x value will usually be greater than tgo(x) because it will mostly bend upwards

    initialtheta = -(
        spice.vsep(MEX - TGO, MEX)
    )  #this will produce and angle that is likly not going to be exactly on
    #the original propagation path, you compare to this if there is a drifting error, as both this and the resultant refracted ray
    # have the same bias error. THIS ANGLE IS BENDING ANTICLOCKWISE IN THIS FRAME (BENDING UPWARDS)
    nicetohave = np.degrees(initialtheta)
    unit = 1  # in km
    rotationvector = np.array(((np.cos(initialtheta), -np.sin(initialtheta)),
                               (np.sin(initialtheta), np.cos(initialtheta))))

    #get unit vecotr of -MEX (then add this vecotr to MEX for each alt calcultation)
    unitmex = -mex / barry2mex  #unit direction (2d)
    direction = unitmex.dot(
        rotationvector
    ) * unit  #make a 2d vector coming from MEX YOU DO NOT KNOW WHAT WAY THIS IS ROTATING

    stage = 0
    t = 0  # index counter for steps along the ray
    while stage < 2:  #==0first unit, so move two units. ==1 propergate step by step. ==2 exit and analyse entire path
        #take the alt at 10 possitions across this unit
        if stage == 0:
            for k in range(10):
                point = mex + (k * (direction / 10))
                #_,_,miniray[k] = spice.recgeo(point, equatorialradii,flatteningcoefficient)
                miniray[k] = np.linalg.norm(point) - 3389
            N0 = findrefractivity(miniray, 10)
            raystep[:, t] = point  #save the last location
            t = t + 1
            stage = stage + 1

        if t == 5440:
            print('stophere')

        if stage == 1:
            for k in range(10):
                point = raystep[:, t - 1] + (k * (direction / 10))
                #_,_,miniray[k] = spice.recgeo(point, equatorialradii,flatteningcoefficient)  #THIS ONLY WORKS IN 3D
                # IMPLEMENTING MARS AS A SIMPLE CIRCLE OF AVERAGE 3389 KM RADIUS, !THIS WILL BE UPDATED TO ELLIPSE!
                miniray[k] = np.linalg.norm(point) - 3389
            raystep[:,
                    t] = point  #9 is the end of the unit, and refraction always happens relative to the center of refractivity, so rotate off this vector
            N1 = findrefractivity(miniray, 10)

            if point[1] < 0:  #if the position drops below the x axis
                stage = stage + 1  #increase the stage value so the while loop is exited

            #DEBUGGING CATCHES:
            r = N0 / N1
            if r == 1:  #only bend when there is a refractive gradient between consecutive air volumes
                t = t + 1
                N0 = N1
                continue

            #find the angle between the unit (air volume) boarder and the current direction
            unitrotationaxis = -(raystep[:, t] / np.linalg.norm(raystep[:, t]))
            #unitdirection = direction #MAYBE ALTERING UNITS WILL EFFECT THIS * OR / BY UNIT, CANT FIGURE OUT NOW, OK WHEN UNIT =1
            DotProduct = np.dot(unitrotationaxis, direction)
            AngleofIncidence = (math.pi / 2) - np.arccos(
                DotProduct)  #angle it enters the next air volume
            #simple snell law to find the bending angle (should be tiny angle)
            AngleofRefraction = np.arcsin(r * np.sin(AngleofIncidence))
            # THIS IS NOT EXACTLY WHAT THE TURN IN DIRECTION IS, NEED TO THINK ABOUT
            rotateby = 300 * ((AngleofIncidence - AngleofRefraction)
                              )  #+ve =clockwise, -ve=anticlockwise

            INCIDENCEDEGREES = np.degrees(AngleofIncidence)
            REFRACTIONDEGREES = np.degrees(AngleofRefraction)
            ROTATIONDEGREES = np.degrees(rotateby)

            #an if statement is required, if this
            if ROTATIONDEGREES > 1 or ROTATIONDEGREES < -1:
                print('stophere, u r bending to much')
            rotationvector = np.array(
                ((np.cos(rotateby), -np.sin(rotateby)), (np.sin(rotateby),
                                                         np.cos(rotateby))))
            direction = direction.dot(rotationvector)

            t = t + 1
            N0 = N1

    error = np.zeros(t)
    g = 0

    #INSERT THE SOLUTION HERE:
    #method4
    error = finderror(raystep, UnrefractedRay, initialtheta)

    #smoothraystep  = interpolate.interp1d(raystep[0],raystep[1], kind = 'linear')
    #smoothUnrefractedRay  = interpolate.interp1d(UnrefractedRay[0],UnrefractedRay[1], kind = 'linear')
    # i=0
    # raysteparea = np.trapz(raystep[0:6482], axis =0)
    # unrefractedarea = np.trapz(UnrefractedRay[0:6482], axis =0)
    # #for i in range(6482):

    # error[i] = raysteppoint[0]-UnrefractedRaypoint[0] + 0.25
    #x_progress  = np.floor(range(int(np.floor(max(raystep[0,:])) - np.floor(min(raystep[0,:])))) + raystep[0,0])
    # raystep_xy = np.zeros((2,np.size(x_progress)))
    #UnrefractedRay_xy = np.zeros((2,np.size(x_progress)))
    error = np.zeros(t)
    # smallerraystep_x = raystep[0,:]
    # smallerraystep_x = smallerraystep_x[smallerraystep_x != 0]
    # roundedraysteps_x = np.floor(smallerraystep_x)
    # roundedunrefracted_x = np.floor(UnrefractedRay[0,:])

    # #rounded_x = np.floor(UnrefractedRay[0,:])
    # #analyse at each raystep to see the error, you can do an x value match and then correct for the delay
    #for i in range(t):

    # raystep_x = raystep[0,:]
    # UnrefractedRay_x = UnrefractedRay[0,:]
    # idxray = np.searchsorted(UnrefractedRay_x , raystep_x[i], side ='left')
    # if idxray > UnrefractedRay_x[-1]:
    #     break
    # UnrefractedRay_xy = UnrefractedRay[:,idxray]
    # #idxUnrefracted = np.searchsorted(roundedunrefracted_x,  x_progress[i], side ='left') # and raystep_x[0,:] > x_progress[i] ]# to deal with the high precision numbers
    # if idxray ==[]:
    #     break

    #ray_y = raystep[1,idxray]
    #unrefracted_y = UnrefractedRay[1,idxUnrefracted]
    #error[i] = ray_y-unrefracted_y

    # index_xUnrefracted= np.where(UnrefractedRay[0,:] < (x_progress[i] + 1) and UnrefractedRay[0,:] > x_progress[i] ) # it is not found because Ray has many decimal points
    # index_xRefracted = np.where(raystep[0,:] < (x_progress[i] +1) and raystep[0,:] > x_progress[i]) #maybe add a -+ limiter aound it?
    # indexU = index_xUnrefracted[1][0]
    # UnrefractedRaypoint = UnrefractedRay[:,indexU]
    # indexR = index_xRefracted[1][0]
    # raysteppoint = raystep[:,indexR]

    #     error[i] = raysteppoint[0]-UnrefractedRaypoint[0] + 0.25
    # #     #PossiblePoints = PossiblePoints[PossiblePoints > x_progress[i] ]# splitting the conditional across two lines due to a ambiguity error
    # #     #PossiblePoints1 = PossiblePoints[PossiblePoints !=0]

    # error = error[error !=0]

    # #LETS SMOOTH THE ERRORS TO FIX YOUR ROUNDING ERROR
    # window_size = 10
    # i = 0
    # moving_averages = []
    # while i < len(error) - window_size + 1:
    #     this_window = error[i : i + window_size]
    #     window_average = sum(this_window) / window_size
    #     moving_averages.append(window_average)
    #     i += 1

    # plt.plot( moving_averages , '.')
    # plt.ylabel('Error')
    # plt.xlabel('X progression')
    # plt.show()

    #     # #F**K IT EVEN OLDER VERSION
    #     # #FIND VALUE IN RAYSTEP THAT SUITS THE XPROGRESS CONDITIONS
    #     # raystep_x = raystep[0,:]
    #     # PossiblePoints = raystep_x[raystep_x < (x_progress[i]+1)]# and raystep_x[0,:] > x_progress[i] ]# to deal with the high precision numbers
    #     # PossiblePoints = PossiblePoints[PossiblePoints > x_progress[i] ]# splitting the conditional across two lines due to a ambiguity error
    #     # PossiblePoints1 = PossiblePoints[PossiblePoints !=0]
    #     # if PossiblePoints.size ==0:#very rarely there maybe be no value for this value of x
    #     #     continue #we might have got to the end
    #     # SearchFor = PossiblePoints1[0]# only take the first value
    #     # index = np.where(raystep_x == SearchFor)
    #     # Index1 = index[0][0]
    #     # raystep_xy[:,i] = raystep[:,Index1]

    #     # #FIND VALUE IN UNREFRACTED THAT SUITS THE XPROGRESS CONDITIONS
    #     # UnrefractedRay_x = UnrefractedRay[0,:]
    #     # PossiblePoints = UnrefractedRay_x[UnrefractedRay_x< (x_progress[i]+1)]# to deal with the high precision numbers
    #     # PossiblePoints = PossiblePoints[PossiblePoints > x_progress[i] ]# splitting the conditional across two lines due to a ambiguity error
    #     # PossiblePoints2 = PossiblePoints[PossiblePoints !=0]
    #     # if PossiblePoints.size ==0:
    #     #     continue
    #     # SearchFor = PossiblePoints2[0]# only take the first value
    #     # index = np.where(UnrefractedRay_x == SearchFor)
    #     # Index2 = index[0][0]
    #     # UnrefractedRay_xy[:,i] = UnrefractedRay[:,Index2]

    #     # # OLD METHOD

    #     #error[i] = np.linalg.norm(raystep_xy[:,i]-UnrefractedRay_xy[:,i]) #probs alter the format of 'index'
    #     g=g+1 #only do this if it set!!

    # #error = error[error < 0.1 ]#take every 10th value]
    # #x_progress = x_progress[0::10]
    # plt.plot( error , '.')
    # plt.ylabel('Error')
    # plt.xlabel('X progression')
    # plt.show()

    fig, ax = plt.subplots()
    plt.plot(barry[0], barry[1], 'x')
    plt.annotate('$\u2642$', (barry[0], barry[1]), fontsize=20)
    marsradii = plt.Circle((0, 0), 3389, color='red', fill=False)
    ax.add_artist(marsradii)
    plt.plot(mex[0], mex[1], '.')
    plt.annotate('MEX', (mex[0], mex[1]))
    plt.plot(tgo[0], tgo[1], 'o')
    plt.annotate('TGO', (tgo[0], tgo[1]))
    plt.plot(UnrefractedRay[0], UnrefractedRay[1], ':')
    plt.annotate(
        'Distance = %ikm' % UnrefractedDistance,
        (UnrefractedRay[0, length // 2], UnrefractedRay[1, length // 2]),
        fontsize=8)
    plt.plot(raystep[0], raystep[1], '.')
    plt.gca().set_aspect('equal', adjustable='box')
    ax.set_xlabel('X')
    ax.set_ylabel('Y')
    plt.show()

    print('stophere')
                           "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)))
def straight(ray, initialdirection, MEX, angleseparation, angleprogression,
             initialangle, xyzpoints, sv, interval):
    alt = ray[2, :]

    rawvector = xyzpoints[:, len(xyzpoints) - 1] - xyzpoints[:, 0]
    norm = np.linalg.norm(rawvector)
    rayunit = rawvector / norm
    interval = 1

    marsrad = spice.bodvrd(sv.front, 'RADII', 3)
    flatteningcoefficient = (marsrad[1][0] - marsrad[1][2]) / marsrad[1][0]
    equatorialradii = marsrad[1][0]
    direction = initialdirection
    # everything must have a alt/ equiv dtheta
    poss = np.zeros([3, 10000])
    dtheta = np.zeros([1, 10000])
    possalt = np.zeros([1, 10000])
    poss[:, 0] = MEX
    step = 10  # this will scale down to 0.001 (1m)

    miniray = np.zeros([3, step])
    totalarcdistance = math.floor(alt[1] * angleseparation)  # in km
    angleseparation = angleseparation * (180 / math.pi
                                         )  #we want in degrees now
    #NOTE TO SELF, AT FIRST THE DIFFERNCE IN N MIGHT BE TO SMALL IN THE HIGH ALT. MAYBE JUMP TO WHERE THE IONO BEGINS THEN START THIS LOOP

    for p in range(
            100000 //
            step):  #10,000 is the max distance, this will never be reached
        if p == 0:  #You need a before n and after n so for the begining go forwards two to get a n0 and n1
            point = poss[:,
                         0] + 0  # regeo needs non-strided arrays, this achieved by adding 0 (cheat)
            _, _, possalt[0, p] = spice.recgeo(point, equatorialradii,
                                               flatteningcoefficient)
            dtheta[0, p] = (spice.vsep(point, MEX)) * (
                180 / math.pi)  #should give 0 degrees
            for i in range(step):
                point = poss[:, 0] + (interval * i * direction
                                      )  #make a small 'step' long 3d vector
                miniray[:, i] = spice.recgeo(
                    point, equatorialradii,
                    flatteningcoefficient)  #(lon; lat; alt)
            n0 = findrefractivity(miniray, step)

        else:
            n0 = n1

        for i in range(step):
            point = poss[:, p] + (interval * i * direction
                                  )  #make a small 'step' long 3d vector
            miniray[:, i] = spice.recgeo(point, equatorialradii,
                                         flatteningcoefficient)
        n1 = findrefractivity(miniray, step)

        #VECTOR CLACS [dont understand inuitivly, boiler plate] maybe this breaks if no r
        #SHOULD HIT THE IONO AT P=332, THIS MAKES SENSE
        r = n0 / n1

        nextposs = poss[:, p] + (
            interval * step * direction
        )  #move along this arc for 1km  then recalc your position
        poss[:, p +
             1] = nextposs  #vsep doesnt allow strided arrays, so make a temp variable
        _, _, possalt[0, p + 1] = spice.recgeo(nextposs, equatorialradii,
                                               flatteningcoefficient)
        #need to have a catch for when the dtheta has been satisfied
        dtheta[0, p + 1] = (spice.vsep(nextposs, MEX)) * (180 / math.pi)
        if dtheta[0, p] > angleseparation:
            break

    #shorten the arrays to just contain the non-zeros values
    dtheta = dtheta[dtheta != 0]

    possalt = possalt[possalt != 0]
    straighterrors = np.zeros([1, p])

    #ALT AND ANGLE PROGRESSION HAVE THE SAME LENGTH
    t = 0
    alteration = alt[0] - possalt[0]
    for i in range(np.size(angleprogression, 1)):  #up tp 6478
        if t == 93:
            print('stophere')

        if angleprogression[0, i] > dtheta[t] and t < p:
            if t == 0:
                alteration = possalt[t] - alt[i]

            straightrayalt = alt[i]
            bendalt = possalt[t]
            error = straightrayalt - bendalt + alteration
            straighterrors[0, t] = error
            t = t + 1
        else:
            continue

    return straighterrors
def producegeometrymeter(MEX, TGO):
    #maybe completly thin this out, you know this is moslty pointless, what does it actually make

    class SpiceVariables:
        obs = '-41'  # NAIF code for MEX '-74'
        target = '-143'  # NAIF code for TGO ['EARTH'/'SUN'/ a groundstation etc] 'MARS ODYSSEY'
        obsfrm = 'IAU_MARS'
        abcorr = 'NONE'
        crdsys = 'LATITUDINAL'
        coord = 'LATITUDE'
        stepsz = 1.0  # Check every [300] seconds if there is an occultation
        MAXILV = 100000  #Max number of occultations that can be returned by gfoclt
        bshape = 'POINT'
        fshape = 'DSK/UNPRIORITIZED'
        front = 'MARS'
        fframe = 'IAU_MARS'
        TFMT = 'YYYY-MM-DD HR:MN:SC'  # Format that Cosmographia understands

    sv = SpiceVariables()

    #THIS COULD BE REMOVED
    # [TGO, _] = spice.spkpos(sv.front, et-when, sv.fframe, 'NONE', sv.target)
    # [MEX, _] = spice.spkpos(sv.front, et-when, sv.fframe, 'NONE', sv.obs)
    TGO = TGO + 0
    MEX = MEX + 0  #force to be non-strided
    dist = math.floor(spice.vdist(TGO, MEX))

    angleseparation = (spice.vsep(MEX, TGO))  # angle taken a mars center
    initialangle = (spice.vsep(-MEX, (TGO - MEX))) * (
        180 / math.pi
    )  # angle taken at mars-MEX-tgo, that points to tgo. needed for the bending functions original starting angle
    #script needs to work via periods of ray and not meters. [totalperiods is the main iterable, not meters]

    sc2sc = TGO - MEX
    norm = np.linalg.norm(sc2sc)
    unitsc2sc = sc2sc / norm  #this needs to shrink if the repeatable expands
    points = np.empty([3, dist])
    sza = np.empty([1, dist])
    angleprogression = np.empty([1, dist])

    xyzpoints = np.zeros([3, dist])
    marsrad = spice.bodvrd(sv.front, 'RADII', 3)
    flatteningcoefficient = (marsrad[1][0] - marsrad[1][2]) / marsrad[1][0]
    equatorialradii = marsrad[1][0]
    # find direction of sun, it will not change much during the occultation. so only calc it once
    #[SUN, _] = spice.spkpos(sv.front, et, sv.fframe, 'NONE', 'SUN')
    for i in range(dist):
        xyzpoint = MEX + (
            i * unitsc2sc
        )  #move along ray, 1000 wavelength distance at a time (685 m). but unitsc2sc is in km...
        xyzpoints[:, i] = xyzpoint
        #sza[0,i] = spice.vsep(SUN,xyzpoint)
        angleprogression[0, i] = (spice.vsep(xyzpoint, MEX)) * (180 / math.pi)
        points[:, i] = spice.recgeo(xyzpoint, equatorialradii,
                                    flatteningcoefficient)
        points[0, i] = (points[0, i] * (-180 / math.pi))
        points[1, i] = (points[1, i] * (-180 / math.pi))

    # ray = np.concatenate((points,sza), axis=0) # important for when sza is included

    #plt.plot(angleprogression[0,:], ray[2,:])
    #plt.show()

    # ray is in lat/lon/alt + sza and xyzpoints is cartesian, both describe the same thing
    return initialangle, xyzpoints
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
dt = -12 * hour   # What is the offset of our observation, from KBO C/A. K-1h, K+2d, etc.

ddt = 1*minute   # Time offset for my calculation of velocity

width_pix_rad_4x4 = 4 * (0.3*hbt.d2r / 1024)  # LORRI 4x4 pixel size, in radians
width_pix_rad_1x1 =     (0.3*hbt.d2r / 1024)  # LORRI 4x4 pixel size, in radians

et_0 = et_ca + dt

(state_0, lt_0) = sp.spkezr('MU69', et_0,            'J2000', 'LT+S', 'New Horizons')
(state_1, lt_1) = sp.spkezr('MU69', et_0 + ddt, 'J2000', 'LT+S', 'New Horizons')

(junk, ra_0, dec_0) = sp.recrad(state_0[0:3])
(junk, ra_1, dec_1) = sp.recrad(state_1[0:3])

omega_kbo = sp.vsep(state_0[0:3], state_1[0:3]) / ddt  # Radians per second of sky motion that the KBO has, from NH

dist_kbo = sp.vnorm(state_0[0:3])  # Distance to KBO, in km

# Calculate the shadow velocity of the KBO

v_shadow_kbo = omega_kbo * dist_kbo  # km/sec of the shadow

# Calculate the time resolution of the LORRI driftscan.
# That is, how long does it take for LORRI to drift one 4x4 LORRI pixel?
# [A: Basically one sec: 0.681 sec, technically.]

dt_lorri_driftscan = width_pix_rad_4x4 / rate_drift

# Calculate Roche radius 
Example #31
0
num_et = 100


et_arr = hbt.frange(et_limits_arr[0], et_limits_arr[1], num_et)

phase_arr = []
utc_arr   = []

for et in et_arr:
    (st, lt) = sp.spkezr('MU69', et, 'J2000', 'LT+S', 'New Horizons')
    vec_sc_mu69 = st[0:3]

    (st, lt) = sp.spkezr('MU69', et, 'J2000', 'LT+S', 'Sun')
    vec_sun_mu69 = st[0:3]
    
    ang_phase = sp.vsep(-vec_sc_mu69, -vec_sun_mu69)
    
    phase_arr.append(ang_phase)
    
    utc_arr.append(sp.et2utc(et, 'C', 0))
    
    print(f'Phase angle = {ang_phase*hbt.r2d} deg at {utc_arr[-1]}')

phase_arr = np.array(phase_arr)
et_arr = np.array(et_arr)

d_et = f'Days past {utc_limits_arr[0]}'
d_et = (et_arr - np.amin(et_arr))/86400

hbt.figsize(8,8)
hbt.fontsize(12)
def bending(ray, initialdirection, MEX, angleseparation, angleprogression,
            initialangle, xyzpoints, sv):
    alt = ray[2, :]

    #THIS IS AN ATMOSPHERE TEST
    # ionoresidual = atmosphere.iono(ray,np.size(ray,1))
    # neutralresidual = atmosphere.neutral(ray,np.size(ray,1))
    # residual = 1 + (ionoresidual + neutralresidual)

    # plt.plot(angleprogression[0,:],residual[0,:])
    # plt.title("Refractive Index through Propergation of Ray")
    # plt.xlabel("MEX -> Possition angle (degrees)")
    # plt.ylabel("Refractive Index")
    # plt.show()

    rawvector = xyzpoints[:, np.size(xyzpoints, 1) - 1] - xyzpoints[:, 0]
    norm = np.linalg.norm(rawvector)
    rayunit = rawvector / norm  # THIS IS SLIGHTLY DIFFERENT TO THE RAY, THERE ARE PYTHON ROUNDING ERRORS

    marsrad = spice.bodvrd(sv.front, 'RADII', 3)
    flatteningcoefficient = (marsrad[1][0] - marsrad[1][2]) / marsrad[1][0]
    equatorialradii = marsrad[1][0]
    direction = rayunit
    # everything must have a alt/ equiv dtheta
    poss = np.zeros([3, 100000])
    straightray = np.zeros([3, 100000])
    poss[:, 0] = MEX
    straightray[:, 0] = MEX
    iterationmax = 0
    miss = 0
    glitchcount = 0
    totalarcdistance = math.floor(alt[1] * angleseparation)  # in km
    angleseparation = angleseparation * (180 / math.pi
                                         )  #we want in degrees now
    #NOTE TO SELF, AT FIRST THE DIFFERNCE IN N MIGHT BE TO SMALL IN THE HIGH ALT. MAYBE JUMP TO WHERE THE IONO BEGINS THEN START THIS LOOP

    while iterationmax <= 100:
        dtheta = np.zeros(
            100000
        )  # must be re-initialised as the vectors are filtered for 0 towards the end of this loop
        possalt = np.zeros(100000)
        straightpossalt = np.zeros(100000)
        #alter initial angle slightly according to miss
        #if miss ~0 dont alter angle, if miss +ve then bend down, (you will likely be bending up initially due to tropo)

        if miss == 0:
            print('.')  # continue will make a 10-line loop with while
        else:
            angle = np.radians(miss *
                               0.03)  # a miss of one km (0.3km = 2 degrees )
            #alter the initail starting angle
            unitposs = MEX / np.linalg.norm(
                MEX)  #vector going upwards radially if n0>n1
            unitdirection = initialdirection / np.linalg.norm(initialdirection)
            rotationaxis = np.cross(
                unitposs, unitdirection
            )  #if direction is going rightwards, this rotation vector is going away from the viewer
            rotationvector = rotationaxis * angle  #must be in degrees
            rotation = R.from_rotvec(rotationvector)
            direction = rotation.apply(
                initialdirection
            )  # produce the slightly re-angled new direction (tiny difference)

        step = 10  # this will scale down to 0.001 (1m)
        interval = 1
        miniray = np.zeros([3, 10])
        constantdirection = direction
        for p in range(
                100000 //
                step):  #10,000 is the max distance, this will never be reached
            if p == 0:  #You need a before n and after n so for the begining go forwards two to get a n0 and n1
                point = poss[:,
                             0] + 0  # regeo needs non-strided arrays, this achieved by adding 0 (cheat)
                _, _, possalt[p] = spice.recgeo(point, equatorialradii,
                                                flatteningcoefficient)
                dtheta[p] = (spice.vsep(point, MEX)) * (
                    180 / math.pi)  #should give 0 degrees

                for i in range(step):
                    point = poss[:,
                                 0] + (interval * i * direction
                                       )  #make a small 'step' long 3d vector
                    miniray[:, i] = spice.recgeo(
                        point, equatorialradii,
                        flatteningcoefficient)  #(lon; lat; alt)
                n0 = findrefractivity(miniray, step)

            elif p == 477:
                print('inspect from here')

            else:
                n0 = n1

            for i in range(step):
                point = poss[:, p] + (interval * i * direction
                                      )  #make a small 'step' long 3d vector
                miniray[:, i] = spice.recgeo(point, equatorialradii,
                                             flatteningcoefficient)
            n1 = findrefractivity(miniray, step)

            #VECTOR CLACS [dont understand inuitivly, boiler plate] maybe this breaks if no r
            #SHOULD HIT THE IONO AT P=332, THIS MAKES SENSE
            r = n0 / n1
            if r > 2 or r < 0.5:
                print('stop here cause neutral is causing huge bending')

            direction, hasglitched = newdirection(poss[:, p], direction, r, p)

            if hasglitched == 1:
                glitchcount = glitchcount + 1

            nextposs = poss[:, p] + (
                interval * step * direction
            )  #move along this arc for 1km  then recalc your position
            nextstraightposs = straightray[:, p] + +(interval * step *
                                                     constantdirection)
            straightray[:, p + 1] = nextstraightposs
            poss[:, p +
                 1] = nextposs  #vsep doesnt allow strided arrays, so make a temp variable
            _, _, possalt[p + 1] = spice.recgeo(nextposs, equatorialradii,
                                                flatteningcoefficient)
            _, _, straightpossalt[p + 1] = spice.recgeo(
                nextstraightposs, equatorialradii, flatteningcoefficient)

            #need to have a catch for when the dtheta has been satisfied
            dtheta[p + 1] = (spice.vsep(nextposs, MEX)) * (180 / math.pi)
            if dtheta[p] > angleseparation:
                break

        #shorten the arrays to just contain the non-zeros values
        dtheta = dtheta[dtheta != 0]

        possalt = possalt[possalt != 0]
        straightpossalt = straightpossalt[straightpossalt != 0]
        errors = np.zeros([1, p])
        straighterrors2 = np.zeros([1, p])
        straightrayalt = np.zeros([1, p])

        #ALT AND ANGLE PROGRESSION HAVE THE SAME LENGTH
        t = 0
        alteration = alt[0] - possalt[0]
        for i in range(np.size(angleprogression, 1)):

            if angleprogression[0, i] > dtheta[t] and t < p:
                if t == 0:
                    alteration = possalt[t] - alt[i]
                    straightalteration = straightpossalt[t] - alt[i]

                straightrayalt[0, t] = alt[i]

                bendalt = possalt[t]
                iteratedstraightrayalt = straightpossalt[t]
                error = straightrayalt[0, t] - bendalt + alteration
                straighterrors2[0, t] = straightrayalt[
                    0, t] - iteratedstraightrayalt + straightalteration
                errors[0, t] = error
                t = t + 1

            else:
                continue

        straighterrors = straight(ray, direction, MEX, angleseparation,
                                  angleprogression, initialangle, xyzpoints,
                                  sv, interval)
        #if straighterrors2 == straighterrors, we can skip this

        straightcut = straighterrors[0, 0:p]
        #errors = errors[0]
        refractedcut = errors[0]
        # there is a glitch when the altitude becomes negative due to the spheriod not being considered. This is a patch to
        # remove the point of inflexion. Further inspection shows that glitches occured in low altitude and not negative altidue, the expnential
        # decay shape of the neutral refractivity profile led to small changes in alt, leading to huge changes in N, thus the refractive
        #  ratio can exceed two and the ray can bend extreamly, veering it of the straight ray and into the martian core, meaning it never touches the 2nd ionsphere

        #This shouldnt be required once a good starting angle is found, only required for graphing early ray propogations (rays with the largest misses)
        # for i in range(p-1): #
        #     if straightcut[i+1] < straightcut[i]:#inflexion
        #         straightcut[i+1] = straightcut[i] + (straightcut[i]-straightcut[i-1])
        #     if refractedcut[i+1] < refractedcut[i]:#inflexion
        #         refractedcut[i+1] = refractedcut[i] + (refractedcut[i]-refractedcut[i-1])

        results = straightcut - refractedcut
        dtheta = dtheta[0:-2]
        results = results[:-1]

        miss = results[
            -3]  #take the last results, this is how many km away the ray is when dtheta is satisfied

        ploterrorvariation(dtheta[0:-2], results[0:-2], straightrayalt)

        iterationmax = iterationmax + 1
        # Plot the boxes
        
        plt.plot((radec_corners[:,0]-ra_jup)*hbt.r2d * (-1), (radec_corners[:,1]-dec_jup)*hbt.r2d,
                 label = f'{index_group}/{index_image}') # Plot all the four points for this box
                
        is_limb_left = (radec_corners[0,0]-ra_jup)>0
        
        if is_limb_left:
            name_limb_arr.append('Left')
        else:
            name_limb_arr.append('Right')

        # 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
Example #34
0
os.chdir(BASE_DIRECTORY)

origin = np.asfarray([0.0, 0.0, 0.0])

#vectorOffsetX = 3.0**(-1/2)
#vectorOffsetY = 3.0**(-1/2)
vectorOffsetX = np.arcsin(1.9992 / sp.dpr())  #2 degrees
vectorOffsetY = np.arcsin(15.0 * 88 / 90 / sp.dpr())  #15
vectorOffsetZ = np.sqrt(1.0 - vectorOffsetX**2 - vectorOffsetY**2)

vector = np.asfarray([vectorOffsetX, vectorOffsetY, vectorOffsetZ])
vectorMagnitude = sp.vnorm(vector)  #should be 1

vector2 = np.asfarray([0.0, 0.0, 1.0])

angleSeparation = sp.vsep(vector / vectorMagnitude, vector2) * sp.dpr()

#X to Z
#vectorA = np.asfarray([vector[0], 0.0, vector[2]])/sp.vnorm([vector[0], 0.0, vector[2]])
vectorA = np.asfarray([vector[0], 0.0, np.sqrt(1.0 - vector[0]**2)])
angleSeparationA = sp.vsep(vectorA / sp.vnorm(vectorA), vector2) * sp.dpr()

#Y to Z
#vectorB = np.asfarray([0.0, vector[1], vector[2]])/sp.vnorm([0.0, vector[1], vector[2]])
vectorB = np.asfarray([0.0, vector[1], np.sqrt(1.0 - vector[1]**2)])
angleSeparationB = sp.vsep(vectorB / sp.vnorm(vectorB), vector2) * sp.dpr()

#X to Y
#vectorC = np.asfarray([vector[0], vector[1], 0.0])/sp.vnorm([vector[0], vector[1], 0.0])
vectorC = np.asfarray([vector[0], vector[1], 0.0]) / sp.vnorm(
    [vector[0], vector[1], 0.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)
def flatbending(xyzpoints, initialangle, MEX, TGO, referencedirection):
    class SpiceVariables:
        obs = '-74'  # NAIF code for MEX
        target = 'MARS ODYSSEY'  # NAIF code for TGO ['EARTH'/'SUN'/ a groundstation etc]
        obsfrm = 'IAU_MARS'
        abcorr = 'NONE'
        crdsys = 'LATITUDINAL'
        coord = 'LATITUDE'
        stepsz = 100.0  # Check every 300 seconds if there is an occultation
        MAXILV = 100000  #Max number of occultations that can be returned by gfoclt
        bshape = 'POINT'
        fshape = 'DSK/UNPRIORITIZED'
        front = 'MARS'
        fframe = 'IAU_MARS'
        TFMT = 'YYYY-MM-DD HR:MN:SC'  # Format that Cosmographia understands

    sv = SpiceVariables()

    #form a coordinate system where tgo is @ y=0 and x= (5000 +norm), Mar's Barrycenter being @ [5000,0]
    subgroupsize = 1
    #initialise non-global variables
    miniray = np.zeros(subgroupsize)
    raystep = np.zeros(
        (2,
         100000000))  # create a large array to populate and then shrink later

    barry2mex = np.linalg.norm(MEX)
    barry2tgo = np.linalg.norm(TGO)

    #find the martian geomoerty so you can reliably find the altitude of a point
    marsrad = spice.bodvrd(sv.front, 'RADII', 3)
    flatteningcoefficient = (marsrad[1][0] - marsrad[1][2]) / marsrad[1][0]
    equatorialradii = marsrad[1][0]

    TGO = TGO + 0
    MEX = MEX + 0  #force to be non-strided
    _, _, MEXalt = spice.recgeo(MEX, equatorialradii, flatteningcoefficient)
    _, _, TGOalt = spice.recgeo(TGO, equatorialradii, flatteningcoefficient)

    #the possition of MEX is found by assuming that it will be somewhere over the relative horizon from TGO
    # (meaning over θ = 90°), finding the angle between the MEX and TGO's negative vector, will give the coords of MEX
    MexRelativeElevation = spice.vsep(-TGO, MEX)  #radians
    mex_y = barry2mex * np.sin(MexRelativeElevation)
    mex_x = barry2mex * np.cos(MexRelativeElevation)

    mex = np.array([0 - mex_x, mex_y])
    tgo = np.array([0 + barry2tgo, 0])
    barry = np.array([0, 0])

    #to plot the non-refracted propogation, we must convert the 3d xyzpoints to 2d, we do this the same way we found the x&y for MEX
    # ,using the norm distance and sep from -TGO5
    length = np.size(xyzpoints, 1)
    UnrefractedDistance = np.linalg.norm(xyzpoints[:, 0] -
                                         xyzpoints[:, -1])  #in km
    UnrefractedRay = np.zeros([2, length])
    for i in range(length):  #conversion to 2D
        point = xyzpoints[:,
                          i] + 0  #need to put vector into temp variable as spice cant handle strided array inputs
        angle = spice.vsep(-TGO, point)
        norm = np.linalg.norm(point)
        point_x = norm * np.cos(angle)
        point_y = norm * np.sin(angle)
        UnrefractedRay[0, i] = 0 - point_x
        UnrefractedRay[1, i] = point_y

    #this will produce and angle that is likly not going to be exactly on
    #the original propagation path, you compare to this if there is a drifting error, as both this and the resultant refracted ray
    # have the same bias error. THIS ANGLE IS BENDING ANTICLOCKWISE IN THIS FRAME (BENDING UPWARDS)
    initialtheta = -(spice.vsep(MEX - TGO, MEX))
    nicetohave = np.degrees(initialtheta)

    #THIS NEEDS TO VARY IF THERE IS AN OVERSHOOT
    unit = 1  # in km
    unitoriginal = unit

    rotationvector = np.array(((np.cos(initialtheta), -np.sin(initialtheta)),
                               (np.sin(initialtheta), np.cos(initialtheta))))

    #get unit vecotr of -MEX (then add this vecotr to MEX for each alt calcultation)
    unitmex = -mex / barry2mex  #unit direction (2d)

    if referencedirection == [0, 0, 0]:
        initialdirection = unitmex.dot(
            rotationvector
        ) * unit  #make a 2d vector coming from MEX YOU DO NOT KNOW WHAT WAY THIS IS ROTATING
    else:  # if there is a value for the fed-back starting direction than use this as the first firection
        initialdirection = referencedirection

    iterationcount = 0
    #while iterationcount<100:
    #print( "Finding Bending Angle (", str(iterationcount) ,"% Complete)")
    errorstore = np.zeros((11, 100000))
    S = np.zeros(20000)

    #IF REFERCEDRECTION==0 DO NORMAL, IF /= INCLUDE THIS AS THE FIRST DIRECTION.

    while iterationcount < 10:
        tic = timer.perf_counter()

        if iterationcount == 0:
            direction = initialdirection

        else:

            missangle = missangle / 1

            missrotationvector = np.array(
                ((np.cos(missangle), -np.sin(missangle)), (np.sin(missangle),
                                                           np.cos(missangle))))

            direction = initialdirection.dot(missrotationvector)
            #check the differecne between the initial and the direction, see if the same for both
            CHECK_ME = direction - initialdirection
            initialdirection = direction

        turningcounter = 0
        stage = 0
        t = 0
        unit = unitoriginal
        #with tqdm(total = mex[1], desc = "Progress", leave=False) as pbar:
        while stage < 2:  #==0first unit, so move two units. ==1 propergate step by step. ==2 exit and analyse entire path
            #take the alt at 10 possitions across this unit

            #lets get a quick calculated for the magnitude of the direction
            MAAAAG = np.linalg.norm(direction)  # this should = unit

            if stage == 0:
                for k in range(subgroupsize):  #this starts with 0
                    point = mex + ((k + 1) * (direction / subgroupsize))
                    #_,_,miniray[k] = spice.recgeo(point, equatorialradii,flatteningcoefficient)
                    miniray[k] = np.linalg.norm(
                        point) - 3389  #average radii of mars

                N0 = findrefractivity(miniray, subgroupsize)
                raystep[:, t] = point  #save the last location
                t = t + 1
                stage = stage + 1

            if stage == 1:
                for k in range(subgroupsize):
                    point = raystep[:, t - 1] + (
                        (k + 1) * (direction / subgroupsize)
                    )  #am i double counting the end of the last and the start of the next?
                    #_,_,miniray[k] = spice.recgeo(point, equatorialradii,flatteningcoefficient)  #THIS ONLY WORKS IN 3D
                    # IMPLEMENTING MARS AS A SIMPLE CIRCLE OF AVERAGE 3389 KM RADIUS, !THIS WILL BE UPDATED TO ELLIPSE!
                    miniray[k] = np.linalg.norm(point) - 3389

                raystep[:,
                        t] = point  #9 is the end of the unit, and refraction always happens relative to the center of refractivity, so rotate off this vector
                N1 = findrefractivity(miniray, subgroupsize)

                #IF THE Y VALUE DROPS BELOW 0, LOOP BACK WITH A STEP SIZE OF 1/10TH#################################################################<- HERE
                if point[1] < 0:  #if the position drops below the x axis
                    direction = direction / 10  #drop the step size
                    unit = unit / 10
                    #stage = stage+1

                    #MAYBE SHRINK THE DIRECTION INSTEAD OF THE UNIT, IT DOESNT GET REINITIALED IN THIS WHILE LOOP
                    # t is not incrememented so that it can loop back to the last position
                    # , t-1 is the position just before crossing over into negative space
                    if abs(point[1]) < 0.00001:  # is it smaller than cm
                        stage = stage + 1  #increase the stage value so the while loop is exited
                    continue

                    # #this section allows for better timing of the function, increment the progresbar by 1 if the current
                    # #position goes one y-value lower
                    # currenty = raystep[1,t]
                    # progress[1] = mex[1] - currenty #this value will be increasing from 0 -> Mex height
                    # increment = np.floor(progress[1])- np.floor(progress[0]) #only when
                    # if increment ==1 :
                    #     pbar.update(1)
                    # progress[0] = progress[1]

                if abs(N1) < 1e-20:  #catch for precision errors
                    S[t] = unit - (
                        N1 * unit
                    )  # whilst neglegible N, Electric distance is can simply be inversly proportional to N
                    t = t + 1
                    N0 = N1
                    continue

                #THE SECTION BELOW IS ONLY ACCESSED WHEN THE REFRACTIVTY IS ABOVE E-20
                #print('Current Y possition is', currenty) #this is alt, so is ~3389 km smaller than the vector
                r = N0 / N1  #NEED MORE PRECISION
                numorator = N0 + 1
                denominator = N1 + 1
                rbending = numorator / denominator  #average would just add 1 to total N
                # if t==5000: #only bend when there is a refractive gradient between consecutive air volumes[NO CHANGE IN DIRECTION]
                #     t=t+1
                #     N0=N1
                #     continue

                #this section is only reached if a turning is going to happen
                # ,testing to see if there is 10 X less turing if the units are 10X smaller -TRUE
                TEST = float(rbending)
                if TEST != 1:
                    turningcounter = turningcounter + 1

                # !! NOW WITH PRECISION !!

                #find the angle between the unit (air volume) boarder and the current direction
                unitrotationaxis = raystep[:, t] / (
                    (np.linalg.norm(raystep[:, t])) * unit)
                #unitdirection = direction #MAYBE ALTERING UNITS WILL EFFECT THIS * OR / BY UNIT, CANT FIGURE OUT NOW, OK WHEN UNIT =1
                DotProduct = np.dot((unitrotationaxis), direction)
                AngleofIncidence = (math.pi / 2) - np.arccos(
                    DotProduct)  #angle it enters the next air volume
                #simple snell law to find the bending angle (should be tiny angle)
                AngleofRefraction = np.arcsin(rbending *
                                              np.sin(AngleofIncidence))
                # THIS IS NOT EXACTLY WHAT THE TURN IN DIRECTION IS, NEED TO THINK ABOUT
                rotateby = ((AngleofIncidence - AngleofRefraction)
                            )  #+ve =clockwise, -ve=anticlockwise

                INCIDENCEDEGREES = np.degrees(AngleofIncidence)
                REFRACTIONDEGREES = np.degrees(AngleofRefraction)
                ROTATIONDEGREES = np.degrees(rotateby)

                #an if statement is required, if this
                if ROTATIONDEGREES > 1 or ROTATIONDEGREES < -1:
                    print('stophere, u r bending to much')
                rotationvector = np.array(
                    ((np.cos(rotateby), -np.sin(rotateby)),
                     (np.sin(rotateby), np.cos(rotateby))))
                direction = direction.dot(rotationvector)

                N0 = N1

                #store N1 to calc electric distance
                S[t] = unit - (N1 * unit)
                t = t + 1

            #pbar.refresh()

        unit_initial = initialdirection / np.linalg.norm(initialdirection)
        dot_product = np.dot(unit_initial, unitmex)
        FinalBendingAngle = np.arccos(dot_product)

        error = np.zeros(t)
        #print("Number of turns:", turningcounter)
        #error = swiftmain.finderror(raystep, UnrefractedRay) # also find the y-overshoot here

        miss = error  # 1D along the abscissa

        #update for the miss angle, going to include miss in the ordinate
        #START EDITING HERE
        miss = point[0] - UnrefractedRay[0, -1]  # X domain

        deltaX = UnrefractedRay[0, -1]  #TGO X value
        deltaY = UnrefractedRay[
            1,
            0]  # this is the height of the whole 2d scene (both refract and unrefacted have the same height)
        unrefractedangle = np.arctan(deltaX / deltaY)
        refractedangle = np.arctan((deltaX + miss) / deltaY)
        missangle = refractedangle - unrefractedangle  # if positive, then rotate clockwise
        #missangle = (np.arcsin(miss/UnrefractedDistance)) # this shouldnt work
        toc = timer.perf_counter()
        passingtime = toc - tic
        #print('miss =', format(miss*1000, '.5f') ,'m || Angle =', np.degrees(missangle) ,
        #                    '° || Speed =',passingtime,' Sec \n', sep = " ", end= " ", flush =True)

        if abs(miss) < 1e-4:  #is the miss smaller than 10 cm?
            #ploterrortraces(errorstore,t)
            break

        iterationcount = iterationcount + 1

    #find the total bending angle at MEX for this final configuration

    #from the refractive profile from MEX ->TGO, calc the intergral of the change in wavelength to aquire doppler
    S = S[S != 0]
    ElectricDistance = (
        np.sum(S)
    )  #N * wavelengths in a km (UNITS MUST BE KEPT THE SAME AS STEP-SIZE)[+ OVERSHOT BECAUSE IT IS A NEGATIVE VARIABLE ]

    return FinalBendingAngle, ElectricDistance, initialdirection  #feedback the starting vector for speed
Example #37
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 #38
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
Example #39
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