Ejemplo n.º 1
0
def find_rotation_angle(et_start, et_end, scan_index, channel):
    """unreliable"""
    
    boresight = BORESIGHTS[channel]
    
    x1 = sp.reclat(find_boresight([et_start], boresight)[0])[1]
    x2 = sp.reclat(find_boresight([et_end], boresight)[0])[1]
    y1 = sp.reclat(find_boresight([et_start], boresight)[0])[2]
    y2 = sp.reclat(find_boresight([et_end], boresight)[0])[2]
    
    #find angle to vertical
    if scan_index == 0:
        rotation_angle = np.arctan((y2-y1)/(x2-x1))
    if scan_index == 1:
    #find angle to horizontal
        rotation_angle = np.arctan((y2-y1)/(x2-x1)) - np.pi/2.
    print(rotation_angle * 180 / np.pi)
    return rotation_angle







        
Ejemplo n.º 2
0
def Calculate_SCoords(run):

    #setup
    import spiceypy as spice
    import numpy as np
    import math
    spice.furnsh("./MetDat/MoonMetdat.txt")

    #import ~_setup.txt and SPK (~.bsp) file
    slines = []
    with open(run + '_setup.txt') as f:
        slines = f.read().splitlines()
    spice.furnsh(run + '.bsp')

    #get TLE_SPK_OBJ_ID and et (time, seconds past J2000) from TLE File
    obj_id = slines[5].split('=')[1]
    et = float(slines[28].split('=')[1])
    print ''
    #read out date as yyyy mmm dd hr:min:sec.millisecond
    print '\n', spice.et2utc(et, 'C', 3)

    #Calculate sub-observer point and distance
    state = spice.spkezr(obj_id, et, "MOON_PA", "LT+S", "Moon")
    s_obs = spice.reclat(state[0][0:3])
    print '\nSub-Observer Point:'
    print ' Sat-Moon distance: ', s_obs[0], 'km'
    print ' Satellite sub-long: ', s_obs[1] * 180 / math.pi, 'deg'
    print ' Satellite sub-lat:  ', s_obs[2] * 180 / math.pi, 'deg'

    #Calculate sub-Earth point and distance
    state = spice.spkezr("Earth", et, "MOON_PA", "LT+S", "Moon")
    s_eat = spice.reclat(state[0][0:3])
    print '\nSub-Earth Point:'
    print ' Earth-Moon distance: ', s_eat[0], 'km'
    print ' Earth sub-long: ', s_eat[1] * 180 / math.pi, 'deg'
    print ' Earth sub-lat:  ', s_eat[2] * 180 / math.pi, 'deg'

    #Calculate sub-Sun point and distance
    state = spice.spkezr("Sun", et, "MOON_PA", "LT+S", "Moon")
    s_sun = spice.reclat(state[0][0:3])
    print '\nSub-Sun Point:'
    print ' Sun-Moon distance: ', s_sun[0], 'km'
    print ' Sun sub-long: ', 90 - s_sun[1] * 180 / math.pi, 'deg'
    print ' Sun sub-lat:  ', s_sun[2] * 180 / math.pi, 'deg\n'

    #Writes selenographic coordiantes to a file named 'run'+_spoints.txt
    with open(run + '_spoints.txt', 'w') as f:
        f.write('' + '\n#Sub-Observer Point:' + '\n\t Sat-Moon distance: ' +
                str(s_obs[0]) + '\n\t slong: ' +
                str(s_obs[1] * 180 / math.pi) + '\n\t slat: ' +
                str(s_obs[2] * 180 / math.pi) + '\n\n#Sub-Earth Point:' +
                '\n\t Earth-Moon distance: ' + str(s_eat[0]) + '\n\t slong: ' +
                str(s_eat[1] * 180 / math.pi) + '\n\t slat: ' +
                str(s_eat[2] * 180 / math.pi) + '\n\n#Sub-Sun Point:' +
                '\n\t Sun-Moon distance: ' + str(s_sun[0]) + '\n\t slong: ' +
                str(90 - s_sun[1] * 180 / math.pi) + '\n\t slat: ' +
                str(s_sun[2] * 180 / math.pi))

    return [slines[21].split('=')[1][1:], et, s_obs, s_eat, s_sun]
Ejemplo n.º 3
0
def reclat(pos):
    """spiceypy.reclat with a wrapper for ndarrays"""

    check_spice_furnsh(keep_previous=True)

    if isinstance(pos, np.ndarray):
        if len(pos.shape) > 1:
            return np.array([spiceypy.reclat(p) for p in pos.T]).T
    return spiceypy.reclat(pos)
Ejemplo n.º 4
0
    def convert_rec2lat(self, coord_rec, degrees=True):
        """
        Function to convert rectangular coordinates to latitudinal coordinates.
        :param coord_rec: Array of rectangular coordinates (in km) to convert. Should 
                          be either a numpy array of len(dates)*3, or a list of floats.
        :param degrees: Boolean, True if units of latitudinal coords should be in degrees.
        :return coord_lat: Array, giving latitudinal position for each rectangular coord. Shape of coor_rec.
        """

        # If coordinates input as a list, then bung them into an array.
        if isinstance(coord_rec, list):
            if all([isinstance(c, (float, int)) for c in coord_rec]):
                coord_rec = np.array(coord_rec)
                coord_rec = np.squeeze(coord_rec)
            else:
                print(
                    "ERROR: coord_src should be a numpy array of coordinates or a list of floats."
                )

        # If coord only has one dimension, set it so that time is zeroth.
        if coord_rec.ndim == 1:
            n_coords = 1
            n_components = coord_rec.size
        else:
            n_coords = coord_rec.shape[0]
            n_components = coord_rec.shape[1]

        # Check coords have 3 components.
        if n_components != 3:
            print(
                "ERROR: Invalid dimension of position vector or state vector")

        # Now convert to latitudinal coords
        if n_coords == 1:
            coord_lat = spice.reclat(coord_rec)
        else:
            # Loop through state to do conversion (as spice.reclat doesn't handle arrays yet)
            coord_lat = np.zeros(coord_rec.shape, dtype=float)
            for i in range(n_coords):
                coord_lat[i, :] = spice.reclat(coord_rec[i, :])

        if degrees:
            if n_coords == 1:
                coord_lat[1:] = np.rad2deg(coord_lat[1:])
            else:
                coord_lat[:, 1:] = np.rad2deg(coord_lat[:, 1:])

        return coord_lat
Ejemplo n.º 5
0
    def point_towards_sun(self, pixel_res=0.5):
        """
        Compute the solar azimuth.

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

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

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

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

        coords = SurfaceCoords.fromtuple(spice.reclat(nB))
        return coords.dlon, coords.dlat
def occSurfaceTrace(et, sv):
    altTrace = 0
    marsrad = spice.bodvrd(sv.front, 'RADII', 3)
    trace = np.zeros([600, 2])
    for i in range(600):  # Find relative positions of TGO and MEX
        [targetpos, _] = spice.spkpos(sv.front, et - i, sv.fframe, 'NONE',
                                      sv.target)
        [sc2scvector, _] = spice.spkpos(sv.target, et - i, sv.fframe, 'NONE',
                                        sv.obs)
        [obspos, _] = spice.spkpos(sv.front, et - i, sv.fframe, 'NONE', sv.obs)

        # Find the unit vector between the SCs
        displacement = math.sqrt(((sc2scvector[0])**2) +
                                 ((sc2scvector[1])**2) + ((sc2scvector[2])**2))
        unitvector = np.true_divide(sc2scvector, displacement)

        # Find the point this unit vector is closest to the Mars
        [rectangularCoords,
         alt] = spice.npedln(marsrad[1][0], marsrad[1][1], marsrad[1][2],
                             targetpos, unitvector)
        [radius, lon, lat] = spice.reclat(rectangularCoords)
        # Rad -> Deg , frame inversion required (hence the negative 180)
        trace[i, 1] = (lon * (-180 / math.pi))
        trace[i, 0] = lat * (-180 / math.pi)

        altTrace = np.append(altTrace, alt)

    altTrace = altTrace[1:]

    return trace, altTrace
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
Ejemplo n.º 8
0
def cassini_surfintercerpt(utc, output=False):
    target = 'TITAN'
    fixref = 'IAU_TITAN'
    dref = 'IAU_TITAN'
    method = 'ELLIPSOID'
    abcorr = 'NONE'
    obsrvr = 'CASSINI'
    state = cassini_phase(utc)
    dvec = spice.vhat(-state[:3])

    et = spice.str2et(utc)

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

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

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

    return point, [radius, colat, lon], spice.vnorm(srfvec)
Ejemplo n.º 9
0
def render_solar_object(solar_object, wireframe):
    if "model" in solar_object:
        model = solar_object["model"]
        if model["type"] == "texture-body":
            sphere = trimesh.creation.uv_sphere(
                radius=solar_object["radius"][0]
            )
            vs = trimesh.creation.uv_sphere().vertices
            uv = []
            for v in vs:
                r, lon, lat = spice.reclat(np.array(v))
                u = (lon + np.pi) / (2.0 * np.pi)
                v = (np.pi / 2.0 - lat) / np.pi
                uv.append([u, v])
            uv = np.array(uv)
            im = Image.open(model["file"])
            sphere.visual = trimesh.visual.TextureVisuals(
                uv=uv,
                image=ImageOps.flip(im),
            )
            mesh = pyrender.Mesh.from_trimesh(
                mesh=sphere, smooth=True, wireframe=wireframe
            )
        elif model["type"] == "model":
            polygon = trimesh.load(model["file"])
            mesh = pyrender.Mesh.from_trimesh(mesh=polygon)
        pose = np.identity(4)
        pose[0:3, 0:3] = solar_object["rotation"]
        pose[0:3, 3] = solar_object["position"]
        return mesh, pose
Ejemplo n.º 10
0
    def convert_to_inst(self, coords):
        """
        Convert the given coordinates to the instrument frame

        Parameters
        ----------
        coords : `astropy.coordinate.SkyCoord`
            The coordinates to transform to the instrument frame
        frame : `str`, optional
            The instrument coordinate frame (ILS or OPT)

        Returns
        -------
        tuple
            x and y coordinates
        """
        icrs_coords = coords.transform_to('icrs')
        cart_coords = icrs_coords.represent_as('cartesian')

        et = spiceypy.datetime2et(coords.obstime.to_datetime())
        sc = spiceypy.sce2c(SOLAR_ORBITER_ID, et)
        cmat, sc = spiceypy.ckgp(SOLAR_ORBITER_STIX_ILS_FRAME_ID, sc, 0, 'J2000')
        vec = cmat @ cart_coords.xyz.value
        # Rotate about z so +x towards the Sun
        # vec = np.array([[-1, 0, 0], [0, -1, 0], [0, 0, 1]]) @ vec
        distance, latitude, longitude = spiceypy.reclat(vec.reshape(3))

        y = (latitude + np.pi) * u.rad
        x = (np.pi/2 - longitude) * u.rad

        return x, y
Ejemplo n.º 11
0
    def point_towards_sun(self, pixel_res=0.5):
        """
        Compute the solar azimuth.

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

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

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

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

        coords = Coords.fromtuple(spice.reclat(nB))
        return coords.dlon, coords.dlat
Ejemplo n.º 12
0
def dataToPickle():
    orbits_begin = {1:'2016-07-31T19:46:02',
                            2:'2016-09-23T03:44:48',
                            3:'2016-11-15T05:36:45',
                            4:'2017-01-07T03:11:30',
                            5:'2017-02-28T22:55:48',
                            6:'2017-04-22T19:14:57'}
    
    file_dict = {}
    metaKernel = 'juno_2019_v03.tm'
    spice.furnsh(metaKernel)

    start_time = datetime.datetime.strptime(orbits_begin[1],'%Y-%m-%dT%H:%M:%S')
    
    end_time = datetime.datetime.strptime(orbits_begin[2],'%Y-%m-%dT%H:%M:%S')
    
    data_folder = pathlib.Path(r'..\data\fgm')
    p = re.compile(r'\d{7}')
    
    for parent,child,files in os.walk(data_folder):
        for name in files:
            if name.endswith('.csv'):
                file_path = os.path.join(data_folder,name)
                
                search = p.search(name).group()
                date = datetime.datetime.strptime(search,'%Y%j')
                
                if date.date() >= start_time.date() and date.date() <= end_time.date():
                    iso_date = date.strftime('%Y-%m-%d')
                    if iso_date not in file_dict.keys():
                        file_dict[iso_date] = [file_path]
                    elif iso_date in file_dict.keys() and file_dict[iso_date] != file_path: 
                        file_dict[iso_date].append(file_path)
    
    for date in file_dict.keys():
        fgmdf = pd.DataFrame(data={'TIME':[],'BX':[],'BY':[],'BZ':[],'LAT':[]})
        save_date = datetime.datetime.strptime(date,'%Y-%m-%d')
        file_list = file_dict[date]
        for file in file_list:
            
            temp = pd.read_csv(file)
            datetime_list = temp['SAMPLE UTC']
            time_list = [datetime.datetime.fromisoformat(i).strftime('%H:%M:%S') for i in datetime_list]
            
            for index,time in enumerate(datetime_list):
                
                position, lighttime = spice.spkpos('JUNO',spice.utc2et(time),'IAU_JUPITER','NONE','JUPITER')
            
                vectorPos = spice.vpack(position[0],position[1],position[2])
                radii,longitude,latitude = spice.reclat(vectorPos)
                lat = latitude*spice.dpr()
                
                if lat >= -10 and lat <= 10:
                    fgmdf = fgmdf.append({'TIME':time,'BX':temp['BX PLANETOCENTRIC'][index],'BY':temp['BY PLANETOCENTRIC'][index],'BZ':temp['BZ PLANETOCENTRIC'][index],'LAT':lat},ignore_index=True)
        fgmdf = fgmdf.sort_values(by=['TIME'])
        save_name = f'{save_date.strftime("%Y%m%d")}'
        save_path = pathlib.Path(f'..\data\pickledfgm\jno_fgm_{save_name}.pkl')
        pickledf = fgmdf.to_pickle(save_path)
        print(f'Saved pickle {date}')                                     
Ejemplo n.º 13
0
    def sc_pos(self, timestr):

        # Convert the timestring to ephemeris time
        self.et = spice.str2et(str(timestr))
        self.et = self.et + self.time_offset

        self.data['sc_wrt_planet'], self.data['target_light_time'] = spice.spkpos(self.spacecraft, self.et, self.iref, 'NONE', self.target)
        self.data['target_distance'] = spice.vnorm(self.data['sc_wrt_planet'])
        self.data['sc_wrt_sun'], light_times = spice.spkpos(self.spacecraft, self.et, self.iref, 'NONE', 'SUN')

        #self.ticks = spice.sce2t(self.sc_id, self.et)

        # Retrieve the position of the spacecraft relative to the target 
        state, self.ltcorr = spice.spkezr(self.target, self.et, self.iref, self.abcorr, self.spacecraft)
        scloc = state[0:3]

        # Get the sub-spacecraft coordinates
        point, epoch, vector = spice.subpnt('NEAR POINT/ELLIPSOID', self.target, self.et, self.framestring, self.abcorr, self.spacecraft)
        self.distance, self.data['lon_sc'], self.data['lat_sc'] = spice.reclat(point)

        # Get the localtime of the spaceship        
        hr, min, sc, time, ampm = spice.et2lst(self.et, self.target_id, self.data['lon_sc'], 'PLANETOCENTRIC')
        self.data['localtime_sc'] = hr + min / 60.0 + sc / 3600.0
        
        # Get the subsolar coordinates
        point, epoch, vector = spice.subslr('NEAR POINT/ELLIPSOID', self.target, self.et, self.framestring, self.abcorr, self.spacecraft)
        distance, self.data['lon_sun'], self.data['lat_sun'] = spice.reclat(point)

        # Convert angles from radians to degrees
        for key in ['lat_sc', 'lon_sc', 'lat_sun', 'lon_sun'] : self.data[key] = np.rad2deg(self.data[key])
        for key in ['lon_sc', 'lon_sun'] : self.data[key] = self.data[key] % 360
	
        state_planet = spice.spkssb(self.target_id, self.et, self.iref)
        inert2p = spice.pxform(self.iref, self.framestring, self.et)
        self.scloc = np.matmul(-inert2p, scloc) 

        self.i2p = spice.pxform(self.instrument, self.framestring, self.et)
        
        return self.data
Ejemplo n.º 14
0
def getPosLabels(dataDict, num):
    stamp = str(dataDict['DATETIME_ARRAY'][min(
        range(len(dataDict['TIME_ARRAY'])),
        key=lambda j: abs(dataDict['TIME_ARRAY'][j] - num))])

    position, lighttime = spice.spkpos('JUNO', spice.utc2et(stamp),
                                       'IAU_JUPITER', 'NONE', 'JUPITER')

    vectorPos = spice.vpack(position[0], position[1], position[2])
    radii, longitude, latitude = spice.reclat(vectorPos)
    lat = f'{round(latitude*spice.dpr(),2)}$^o$ Lat'
    dist = f'{round(radii/69911,3)} $R_j$'
    return lat, dist
Ejemplo n.º 15
0
def caps_ramdirection_azielv(tempdatetime, observ='titan'):
    """
    Returns the azimuth and elevation of the ramdirection
    """
    ram_unit = cassini_ramdirection_SCframe(tempdatetime, observ=observ)

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

    return ram_unit_azielv[0], ram_unit_azielv[1]
Ejemplo n.º 16
0
def bf2latlon(rs):
    '''
	Calculate latitude / longitude coordinates
	from body-fixed vectors
	'''

    steps = rs.shape[0]
    latlons = np.zeros(rs.shape)

    for step in range(steps):
        r_norm, lon, lat = spice.reclat(rs[step])
        latlons[step] = [lat * r2d, lon * r2d, r_norm]

    return latlons
Ejemplo n.º 17
0
    def vector_params(self, vector) : 

        ret = np.full(shape = [len(self.keys)], fill_value = -1000.0)
        
        # Calculate the distance of the boresight and the center of the target
        origin = np.array([0.0, 0.0, 0.0])
        nearpoint, rayradius = spice.nplnpt(self.scloc, vector, origin)

        # Get the intercept to calculate the radius of of the target 
        normal = spice.surfpt(origin, 1000.0 * nearpoint, self.radii[0], self.radii[1], self.radii[2])
        ret[self.map['limb_distance']] = rayradius - spice.vnorm(normal)
    
        # Calculate the 'occultation' latitude and longitude 
        ret[self.map['bodyintercept']], ret[self.map['limb_lat']], ret[self.map['limb_lon']] = spice.reclat(nearpoint) 

        # Test if the boresight intersects with our target surface 
        try: 
            point = spice.surfpt(self.scloc, vector, self.radii[0], self.radii[1], self.radii[2])
            intercept = True
        except: 
            intercept = False
    
        if (intercept) : 
        
            # Get some angles 
            ret[self.map['phase']], ret[self.map['incidence']], ret[self.map['emission']] = spice.illum(self.target, self.et , self.abcorr, self.spacecraft, point)

            # Calculate the planetocentric coordinates 
            distance, ret[self.map['lon']], ret[self.map['lat']] = spice.reclat(point)
            ret[self.map['losdist']] = spice.vnorm(self.scloc - point)
            
             # Calculate the planetographic coordinates
            ret[self.map['lon_graphic']], ret[self.map['lat_graphic']], bodyintercept = spice.recpgr(self.target, point, self.radii[0], self.flattening)
            
            # Get the localtime, and convert to decimal hours 
            hr, min, sc, time, ampm = spice.et2lst(self.et, self.target_id, ret[self.map['lon']], 'PLANETOCENTRIC')
            ret[self.map['localtime']] = hr + min / 60.0 + sc / 3600.0
            
        # For the angles, convert radians to degrees
        for key in self.angles : 
            if (ret[self.map[key]] != -1000.0) : 
                    ret[self.map[key]] = np.rad2deg(ret[self.map[key]])

        # Makes sure longitudes wrap 0 to 360, spice returns the Earth-like -180 to 180. 
        longitudes = ['lon', 'limb_lon', 'lon_graphic']
        for key in longitudes : 
            ret[self.map[key]] = ret[self.map[key]] % 360

        return np.array(ret)
Ejemplo n.º 18
0
    def positionData(self):
        # spice.furnsh(self.meta) #loads the meta kernel that will load all kernels needed

        time = spice.utc2et(self.time)

        self.position, lighttime = spice.spkpos(
            'JUNO', time, 'IAU_JUPITER', 'NONE', 'JUPITER'
        )  #Finds the position in cartesian coords relative to jupiter

        pos = spice.vpack(self.position[0], self.position[1],
                          self.position[2])  #Packs the position into a vector
        self.distance, self.longitude, self.latitude = spice.reclat(
            pos)  #Finds radial dist, latitide and longitude
        self.latitude *= spice.dpr()
        self.distance /= 69911
def Location(et, 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.target)
    [sc2scvector, _] = spice.spkpos(sv.target, et - when, sv.fframe, 'NONE',
                                    sv.obs)
    displacement = np.linalg.norm(sc2scvector)
    sc2scunitvector = np.true_divide(sc2scvector, displacement)
    marsrad = spice.bodvrd(sv.front, 'RADII',
                           3)  # Extract the triaxial dimensions of Mars
    # 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)
    [radius, lon, lat] = spice.reclat(nearestpoint)
    lon = lon * (
        -180 / math.pi
    )  # Rad -> Deg , frame inversion required (hence the negative 180)
    lat = lat * (-180 / math.pi)
    #Coords[0] = lon
    #Coords[1] = lat
    return lon, lat, displacement, nearestpoint, alt
Ejemplo n.º 20
0
def check_solar_eclipse_latlons( et, body0, body1, frame = 'J2000' ):
	r_sun2body = spice.spkpos(
		str( body0[ 'SPICE_ID' ] ), et, frame, 'LT', 'SUN' )[ 0 ]
	r = spice.spkpos(
		str( body1[ 'SPICE_ID' ] ), et, frame, 'LT',
		str( body0[ 'SPICE_ID' ] ) )[ 0 ]

	delta_ps    = nt.norm( r_sun2body )
	s_hat       = r_sun2body / delta_ps
	proj_scalar = np.dot( r, s_hat )

	if proj_scalar <= 0.0:
		return -1, None

	proj     = proj_scalar * s_hat
	rej_norm = nt.norm( r - proj )
	umbra    = check_umbra( delta_ps, body0[ 'diameter' ],
		proj_scalar, rej_norm, body1[ 'radius' ] )

	if umbra:
		args = { 'r': r, 's_hat': s_hat, 'radius': body1[ 'radius' ] }
		try:
			sigma = nt.newton_root_single_fd( eclipse_root_func,
				proj_scalar - body1[ 'radius' ], args )[ 0 ]
		except RuntimeError:
			return -1, None

		r_eclipse = sigma * s_hat - r
		r_bf      = np.dot(
			spice.pxform( frame, body1[ 'body_fixed_frame' ], et ),
			r_eclipse )
		latlon        = np.array( spice.reclat( r_bf ) )
		latlon[ 1: ] *= nt.r2d

		return 2, latlon
	else:
		return -1, None
Ejemplo n.º 21
0
def cart2lat( rs, frame_from = None, frame_to = None, ets = None, deg = True ):
	'''
	Calculate latitudinal coordinates given cartesian coordinates
	optionally calculating cartesian coordinates in new frame
	before coordinate conversion
	'''
	if frame_from is not None and frame_from != frame_to:
		rs = frame_transform( rs, frame_from, frame_to, ets )

	steps   = rs.shape[ 0 ]
	latlons = np.zeros( ( steps, 3 ) )

	for step in range( steps ):
		'''
		Note: spice.reclat function returns latitudinal
		coordinates in the following order:
		radial, longitude, latitude
		'''
		latlons[ step ] = spice.reclat( rs[ step ] )

	if deg:
		latlons[ :, 1: ] *= r2d

	return latlons
Ejemplo n.º 22
0
        mars_axes = sp.bodvrd("MARS", "RADII", 3)[1]  #get mars axis values
        line_points = mars2tgo_pos

        #make pointing vectors between tgo and sun positions
        line_vectors = tgo2sun_pos  #np.asfarray([[tgo_vec[0]-sun_vec[0],tgo_vec[1]-sun_vec[1],tgo_vec[2]-sun_vec[2]] for tgo_vec,sun_vec in zip(list(tgo_pos),list(sun_pos))])

        #calculate surface tangent point using pointing vectors and tgo position vs time
        print("Calculating tangent points")
        tangent_coords = np.asfarray([
            sp.npedln(mars_axes[0], mars_axes[1], mars_axes[2], line_point,
                      line_vector)[0] for line_point, line_vector in zip(
                          list(line_points), list(line_vectors))
        ])
        tangent_lonlats1 = np.asfarray([
            sp.reclat(tangent_coord) for tangent_coord in list(tangent_coords)
        ])
        tangent_lonlats = np.asfarray(
            [[tangent_lonlat[1] * sp.dpr(), tangent_lonlat[2] * sp.dpr()]
             for tangent_lonlat in list(tangent_lonlats1)])
        tangent_lsts = [
            sp.et2lst(time, 499, (tangent_lonlat[0] / sp.dpr()),
                      "PLANETOCENTRIC")[3]
            for time, tangent_lonlat in zip(list(times), list(tangent_lonlats))
        ]
        tangent_lsts_hours = np.asfarray([
            np.float(tangent_lst[0:2]) + np.float(tangent_lst[3:5]) / 60.0 +
            np.float(tangent_lst[6:8]) / 3600.0 for tangent_lst in tangent_lsts
        ])

        nadir_lonlats = np.asfarray(
Ejemplo n.º 23
0
    def getFGMData(self,fgm_folder):
        #Mag data for each cooresponding crossing is found
        self.file_dict = {}
        p = re.compile(r'\d{7}')
        for parent,child,files in os.walk(fgm_folder):
            for file_name in files:
                if file_name.endswith('.csv'):
                    file_path = os.path.join(fgm_folder,file_name)
                    date = p.search(file_name).group()
                    date_iso = datetime.datetime.strptime(date,'%Y%j').strftime('%Y-%m-%d')
                    
                    match = self.crossingdf.loc[self.crossingdf['DATE'] == date_iso]
                    self.cdfdf = self.cdfdf.append(match,ignore_index = True)
                    if date_iso not in self.file_dict.keys():
                        self.file_dict[date_iso] = file_path

        #The mag data is sorted through and two hours on either side of the crossing is pulled
        for row in self.cdfdf.itertuples():
            date = row[1]
            time = row[2]
            type = row[3]
            
            crossing_stamp = datetime.datetime.strptime(f'{date}T{time}','%Y-%m-%dT%H:%M:%S')
            if date in self.file_dict.keys():
                
                fgm_data = pd.read_csv(self.file_dict[date])
                time_list = [datetime.datetime.fromisoformat(i) for i in fgm_data['SAMPLE UTC']]
                #crossing_index = min(time_list,key=lambda x: abs(x-crossing_stamp))
                crossing_index_plus = time_list.index(min(time_list,key=lambda x: abs(x-(crossing_stamp+datetime.timedelta(hours=2)))))
                crossing_index_minus = time_list.index(min(time_list,key=lambda x: abs(x-(crossing_stamp-datetime.timedelta(hours=2)))))
                save_time_list = [i.isoformat() for i in time_list[crossing_index_minus:crossing_index_plus+1]]

                spice.furnsh('juno_2019_v03.tm')
                spice_time_list = [spice.utc2et(i) for i in save_time_list]
                position_list = []
                latitude_list = []
                x,y,z=[],[],[]
                for spice_time in spice_time_list:
                    pos,lt = spice.spkpos('JUNO',spice_time,'IAU_JUPITER','NONE','JUPITER')
                    pos_vec = spice.vpack(pos[0],pos[1],pos[2])
                    rad_pos,long,lat = spice.reclat(pos_vec)
                    lat *= spice.dpr()
                    rad_pos /= 69911

                    pos,lt = spice.spkpos('JUNO',spice_time,'IAU_SUN','NONE','JUPITER')
                    x.append(pos[0])
                    y.append(pos[1])
                    z.append(pos[2])

                    position_list.append(rad_pos)
                    latitude_list.append(lat)
                spice.kclear()

                file_save_date = crossing_stamp.strftime('%Y%jT%H%M%S') + f'_{type}'
                cdf_file = pycdf.CDF(f'..\crossings\cdf\jno_mag_{file_save_date}.cdf','')
                cdf_file.attrs['Author'] = 'Andrew Schok'
                cdf_file['TIME'] = save_time_list
                cdf_file['BX DATA'] = fgm_data['BX PLANETOCENTRIC'][crossing_index_minus:crossing_index_plus+1]
                cdf_file['BX DATA'].attrs['units'] = 'nT'
                cdf_file['BY DATA'] = fgm_data['BY PLANETOCENTRIC'][crossing_index_minus:crossing_index_plus+1]
                cdf_file['BY DATA'].attrs['units'] = 'nT'
                cdf_file['BZ DATA'] = fgm_data['BZ PLANETOCENTRIC'][crossing_index_minus:crossing_index_plus+1]
                cdf_file['BZ DATA'].attrs['units'] = 'nT'
                cdf_file['X POSITION'] = x
                cdf_file['X POSITION'].attrs['units'] = 'km'
                cdf_file['Y POSITION'] = y
                cdf_file['Y POSITION'].attrs['units'] = 'km'
                cdf_file['Z POSITION'] = z
                cdf_file['Z POSITION'].attrs['units'] = 'km'
                cdf_file['RADIAL DISTANCE'] = position_list
                cdf_file['RADIAL DISTANCE'].attrs['units'] = 'Rj'
                cdf_file['LATITUDE'] = latitude_list
                cdf_file['LATITUDE'].attrs['units'] = 'deg'

                cdf_file.close()
                print(f'Created CDF for {type} crossing {crossing_stamp.strftime("%Y-%m-%dT%H:%M:%S")}')
Ejemplo n.º 24
0
 def _get_coords(self):
     if not self.spoint_set:
         raise SPointNotSetError
     return Coords.fromtuple(spice.reclat(self.spoint))
Ejemplo n.º 25
0
def get_satellite_position(satname, timestamp, kernelpath=None, kernellist=None, refframe="J2000", refobject='SUN', rlonlat=False):
    """
    Returns satellite position from the reference frame of the Sun.
    Files here:
    https://sohowww.nascom.nasa.gov/solarsoft/stereo/gen/data/spice/depm/ahead/
    and
    https://stereo-ssc.nascom.nasa.gov/data/moc_sds/ahead/data_products/ephemerides/

    Parameters
    ==========
    satname : str
        Name of satellite. Recognised strings: 'stereo'
    timestamp : datetime.datetime object / list of dt objs
        Times of positions to return.
    kernelpath : str (default=None)
        Optional path to directory containing kernels, else local "kernels" is used.
    kernellist : str (default=None)
        Optional list of kernels in directory kernelpath, else local list is used.
    refframe : str (default='J2000')
        See SPICE Required Reading Frames. J2000 is standard, HEE/HEEQ are heliocentric.
    refobject : str (default='Sun')
        String for reference onject, e.g. 'Sun' or 'Earth'
    rlonlat : bool (default=False)
        If True, returns coordinates in (r, lon, lat) format, not (x,y,z).

    Returns
    =======
    position : array(x,y,z), list of arrays for multiple timestamps
        Position of satellite in x, y, z with reference frame refframe and Earth as
        observing body. Returns (r,lon,lat) if rlonlat==True.
    """

    if 'stereo' in satname.lower():
        if 'ahead' in satname.lower() or 'a' in satname.lower():
            satstr = 'STEREO AHEAD'
        if 'behind' in satname.lower() or 'b' in satname.lower():
            satstr = 'STEREO BEHIND'
    elif 'dscovr' in satname.lower():
        satstr = 'DSCOVR'
        logger.error("get_satellite_position: DSCOVR kernels not yet implemented!")
    else:
        satstr = satname.upper()
        logger.warning("get_satellite_position: No specific SPICE kernels for {} satellite!".format(satname))

    if kernellist == None:
        kernellist = required_kernels[satstr]

    if kernelpath == None:
        kernelpath = os.path.join('data/kernels')

    logger.info("get_satellite_position: Reading SPICE kernel files from {}".format(kernelpath))

    for k in kernellist:
        spiceypy.furnsh(os.path.join(kernelpath, k))
    time = spiceypy.datetime2et(timestamp)

    position, lighttimes = spiceypy.spkpos(satstr, time, refframe, 'NONE', refobject)

    if rlonlat:
        if len(position) > 3:
            return np.asarray([spiceypy.reclat(x) for x in position])
        return spiceypy.reclat(position)
    else:
        return position
Ejemplo n.º 26
0
 def coords(self):
     if not self.spoint_set:
         raise SPointNotSetError
     return SurfaceCoords.fromtuple(spice.reclat(self.spoint))
Ejemplo n.º 27
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
Ejemplo n.º 28
0
typein = "PLANETOCENTRIC"
body = 499

etStart = sp.utc2et("2018MAR24-11:50:00 UTC")
etEnd = sp.utc2et("2020MAR24-11:50:00 UTC")

ets = np.linspace(etStart, etEnd, num=180)

writeOutput("UTC Time, LSubS, Longitude, Latitude, Local Solar Time")
for et in list(ets):

    lSubS = lsubs(et)
    terminatorPoints = sp.edterm(trmtyp, source, target, et, ref, abcorr,
                                 observer, npts)

    terminatorLatLonsRad = np.asfarray([
        sp.reclat(terminatorPoint)[1:3]
        for terminatorPoint in terminatorPoints[2]
    ])

    terminatorLatLonsDeg = terminatorLatLonsRad * sp.dpr()

    terminatorLSTs = [
        sp.et2lst(et, body, terminatorLatLonRad[0], typein)[3]
        for terminatorLatLonRad in list(terminatorLatLonsRad)
    ]

    for index, terminatorLST in enumerate(terminatorLSTs):
        writeOutput("%s,%0.2f,%0.2f,%0.2f,%s" %
                    (et2utc(et), lSubS, terminatorLatLonsDeg[index, 0],
                     terminatorLatLonsDeg[index, 1], terminatorLST))
Ejemplo n.º 29
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)
Ejemplo n.º 30
0
     
     pt_closest_jup = -pt_closest + vec_sun_jup
     
     # Now convert this to an elevation angle and distance
     # 'Radius' will be the centerpoint of the image, in km from Jup.
     
     mx = sp.pxform('J2000', 'IAU_JUPITER', t_i['ET'])
     
     # Convert to IAU_JUP coords. Looks good.
     
     pt_closest_jup_jup = sp.mxv(mx, pt_closest_jup)  # Get the close point to Jup, in IAU_JUP coords
     
     # Convert into radius / lon / lat, in km, in Jupiter frame. 
     # ** Seems to be some error here. Radius is OK, but lat not right??
     
     (radius, lon, lat) = sp.reclat(pt_closest_jup_jup)
     
     dist_jup_center_horizontal_rj = math.cos(lat) * radius / rj_km   # Same as dst_jup_center_rj_range, dfft method 
     dist_jup_center_vertical_rj   = math.sin(lat) * radius / rj_km
     
     imagenum +=1 # Loop to next image number in the footprint
     
 dist_proj_rj_foot_arr.append(dist_jup_center_rj_range)  # Set the projected distance, for the footprint
 name_limb_foot_arr.append(name_limb_arr[-1])            # Define the limb direction for the footprint
 
 dist_jup_center_horizontal_rj_foot_arr.append(dist_jup_center_horizontal_rj)
 dist_jup_center_vertical_rj_foot_arr.append(dist_jup_center_vertical_rj)
 
 # Finished this loop over image set (aka footprint)
 
 corner_arr = np.array(corner_arr) / rj_km  # This is now an array (n_pts x 5, 3)
Ejemplo n.º 31
0
#radius = planes['Radius_eq']

radius    = hdulist['Radius_eq'].data
longitude = hdulist['Longitude_eq'].data

et = hdulist['PRIMARY'].header['SPCSCET'] # Get the ET from the file
utc = sp.et2utc(et, 'C', 0)

w = WCS(file)

# Calculate the sub-observer latitude (ie, ring tilt angle)

(vec, lt) = sp.spkezr('New Horizons', et, 'IAU_PLUTO', 'LT', 'Pluto')
vec = vec[0:3]
(junk, lon_obs, lat_obs) = sp.reclat(vec) # Get latitude, in radians

#==============================================================================
# Make a plot of the image + backplanes
#==============================================================================

plt.set_cmap('Greys_r')

hbt.figsize((9,6)) # This gets ignored sometimes here. I'm not sure why??
plt.subplot(1,3,1)
plt.imshow(stretch(im))
plt.title(sequence)
plt.gca().get_xaxis().set_visible(False)

plt.subplot(1,3,2)
plt.imshow(radius, cmap='plasma')
Ejemplo n.º 32
0
    def get_lonlat(self,
                   dates,
                   target,
                   system,
                   observatory=None,
                   corr='NONE',
                   precess=False,
                   degrees=True):
        """
        Function to calculate the radius, longitude and lataitude of a target in coordinate system given by system,
        centered on an observatory. Observatory doesn't always need to be specified, but for some coordinate systems
        needs to be. Doesnt handle velocity components of state vector.
        :param dates: Astropy time object of dates(s) to get
        :param target: String name of target body.
        :param system: String name of coordinate system.
        :param observatory: String name of observatory.
        :param corr: String specifying whether to perform correction for planetary abberation.
        :param precess: Boolean. If true perform a calculation for precession.
        :param degrees: Boolean. If true return latitude and longitude in degrees
        :return coords: Float array of coords.
        """

        # Add in calculation for Helioprojective Cartesian coordinates.
        if system in ['HPC', 'hpc']:
            system = 'RTN'
            calc_hpc = True
        else:
            calc_hpc = False

        if system in ['HPR', 'hpr']:
            system = 'RTN'
            calc_hpr = True
        else:
            calc_hpr = False

        # Get the position of the target for these dates/system/observatory
        state = self.get_coord(dates,
                               target,
                               system=system,
                               observatory=observatory,
                               corr=corr,
                               precess=precess,
                               no_velocity=True)

        # Use spice to convert to lon/lat
        state = np.array(state)

        if state.ndim == 1:
            rad, lon, lat = spice.reclat(state)
        else:
            # Loop through state to do conversion (as spice.reclat doesn't handle arrays yet)
            rad = np.zeros(len(dates), dtype=float)
            lon = np.zeros(len(dates), dtype=float)
            lat = np.zeros(len(dates), dtype=float)
            for i in range(len(dates)):
                rad[i], lon[i], lat[i] = spice.reclat(state[i])

        # Correct HPC coords if necessary
        if calc_hpc:
            lon, lat = self.convert_rtn_to_hpc(lon, lat, degrees=False)

        if calc_hpr:
            lon, lat = self.convert_rtn_to_hpc(lon, lat, degrees=False)
            lon, lat = self.convert_hpc_to_hpr(lon, lat, degrees=False)

        # Correct Carrington longitudes if neccesary
        carrington_names = ['CARR', 'CARRINGTON', 'carr', 'carrington']
        if system in carrington_names:
            if dates.size > 1:
                id_under = lon < 0
                if any(id_under):
                    lon[id_under] += 2 * np.pi

            elif dates.size == 1:
                if lon < 0:
                    lon += 2 * np.pi

        if degrees:
            lon = np.rad2deg(lon)
            lat = np.rad2deg(lat)

        # Make state vector with same structure as output by spkpos
        if dates.isscalar:
            coords = np.array([rad, lon, lat]).T
            # If only one date, loose first dimension
            coords = np.squeeze(coords)
        else:
            rad = np.expand_dims(rad, axis=1)
            lon = np.expand_dims(lon, axis=1)
            lat = np.expand_dims(lat, axis=1)
            coords = np.hstack((rad, lon, lat))
        return coords
def getData(eqdata):
    import spiceypy as spice
    import numpy as np
    import constants as con
    import math

    #Load the meta kernel file for the determination of the position of the planets.
    metakernel = '/home/jdw/UM2019Autumn/M561/Project/kernels/M561ProjectKernels.txt'
    spice.furnsh(metakernel)

    #Find the number of earthquakes to be analyzed.
    nquake = len(eqdata)

    #Create a numpy array with nquake rows and 49 columns.
    data = np.zeros((nquake, 284), dtype='float64')

    #Get the ephemeris times from the earthquake dictionary keys.
    et = list(eqdata.keys())

    #Get the earthquake data from the earthquake dictionary values.
    eqdata = list(eqdata.values())

    #Get the positions
    #First create a vector of planet names to be fed to the spice
    #function.
    Planets = [
        'Sun', 'Mercury_Barycenter', 'Venus_Barycenter', 'Moon',
        'Mars_Barycenter', 'Jupiter_Barycenter', 'Saturn_Barycenter',
        'Uranus_Barycenter', 'Neptune_Barycenter', 'Pluto_Barycenter'
    ]

    #Create a time offset index.  I want to look not only at the exact earthquake
    #time but also times that are a given number of seconds before and after
    #the earthquake.
    k = [3000, 2400, 1800, 1200, 600, 0, -600]

    for i in range(nquake):
        #First fill in the earthquake data.
        data[i][0] = et[i]
        data[i][1] = eqdata[i][0]  #Earthquake Magnitude
        data[i][2] = eqdata[i][1]  #Earthquake Latitude (degrees)
        data[i][3] = eqdata[i][2]  #Earthquake Longitude (degrees)

        #Now loop through the planets to get the distances.
        for p in range(len(Planets)):
            pindex = p * 28 + 4

            #Now loop through the seven times around the earthquake.
            for t in range(7):

                #Create a time value.  The earthquakes are given in the et
                #vector and I want to look at times before those earthquake
                #times.
                time = et[i] - k[t]

                #Determine the position of a given planet with respect to Earth
                #for a given et in the J2000 coordinate system.
                pos, ltime = spice.spkpos(
                    Planets[p],
                    time,
                    'J2000',
                    'NONE',
                    'Earth',
                )

                #Calculate the distance in meters. Vnorm returns a distance in kilometers
                #so we have to convert.  1km = 1000.0 meters.
                dist = spice.vnorm(pos) * 1000.0

                #Return the range, longitude and latitude of the position vector
                #pointing from Earth to the planet.  These are output in radians.
                ran, lon, lat = spice.reclat(pos)

                #Convert the radians to degrees.
                longitude = math.degrees(lon)
                latitude = math.degrees(lat)

                #Get the gravitational force for the times preceeding and
                #following the earthquake.
                F = getGravForce(p, dist)

                #Fill the data array.
                data[i][pindex + t * 4] = dist
                data[i][pindex + t * 4 + 1] = latitude
                data[i][pindex + t * 4 + 2] = longitude
                data[i][pindex + t * 4 + 3] = F

    return data
Ejemplo n.º 34
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 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 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
Ejemplo n.º 37
0
    def convert_lonlat(self,
                       dates,
                       coord_src,
                       system_src,
                       system_dst,
                       observe_src=None,
                       observe_dst=None,
                       degrees=True):
        """
        Function to convert latitudinal coordinates betwen different reference 
        frames.
        :param dates: Astropy time object of dates(s).
        :param coord_src: Array of latitudinal coordinates (rad, lon, lat) to convert. Should 
                          be either a numpy array of len(dates)*3, or a list of floats.
        :param system_src: String name of coordinate system of coord array.
        :param system_dst: String name of coordinate system to transform coord array to.
        :param observe_src: String name of observatory for origin of system from. Only needed for some systems.
        :param observe_dst: String name of observatory for origin of system to. Only needed for some systems.
        :param degrees: Boolean. If true indicates that units of coord_src, and returned coord_dst, are in degrees.
        :return coord_dst: Array, giving state at each dates. Shape of len(dates)x3.
        """

        # If coordinates input as a list, then bung them into an array.
        if isinstance(coord_src, list):
            if all([isinstance(c, (float, int)) for c in coord_src]):
                coord_src = np.array(coord_src)
                coord_src = np.squeeze(coord_src)
            else:
                print(
                    "ERROR: coord_src should be a numpy array of coordinates or a list of floats."
                )

        # If coord only has one dimension, set it so that time is zeroth.
        if coord_src.ndim == 1:
            n_coords = 1
            n_components = coord_src.size
        else:
            n_coords = coord_src.shape[0]
            n_components = coord_src.shape[1]

        # Check dates and coord sizes match.
        if dates.size != n_coords:
            print(
                "Error: Number of dates does not correspond to number of coordinates."
            )

        # Check coords have 3 components.
        if n_components != 3:
            print(
                "ERROR: Invalid dimension of position vector or state vector")

        if (system_src in ['HPC', 'hpc', 'HPR', 'hpr', 'RTN', 'rtn'
                           ]) & (observe_src is None):
            print(
                "ERROR: system_src given as {}, but no observe_src specified. Assuming Earth"
                .format(system_src))
            observer_src = 'earth'

        if (system_dst in ['HPC', 'hpc', 'HPR', 'hpr', 'RTN', 'rtn'
                           ]) & (observe_dst is None):
            print(
                "ERROR: system_dst given as {}, but no observe_src specified. Assuming Earth"
                .format(system_dst))
            observer_dst = 'earth'

        # Parse out the coordinates.
        if dates.isscalar:
            rad = coord_src[0]
            lon = coord_src[1]
            lat = coord_src[2]
        else:
            rad = np.squeeze(coord_src[:, 0])
            lon = np.squeeze(coord_src[:, 1])
            lat = np.squeeze(coord_src[:, 2])

        # Put angles into radians for spice.
        if degrees:
            lon = np.deg2rad(lon)
            lat = np.deg2rad(lat)

        if system_src in ['HPC', 'hpc']:
            # Convert to RTN and updates system_src tag
            lon, lat = self.convert_hpc_to_rtn(lon, lat, degrees=False)
            system_src = 'RTN'
        elif system_src in ['HPR', 'hpr']:
            # Convert to HPC and then to RTN, updates system_src tag
            lon, lat = self.convert_hpr_to_hpc(lon, lat, degrees=False)
            lon, lat = self.convert_hpc_to_rtn(lon, lat, degrees=False)
            system_src = 'RTN'

        # Now convert to rectangular coords.
        if dates.isscalar:
            coord_src_rec = spice.latrec(rad, lon, lat)
        else:
            # Loop through state to do conversion (as spice.reclat doesn't handle arrays yet)
            coord_src_rec = np.zeros(coord_src.shape, dtype=float)
            for i in range(dates.size):
                coord_src_rec[i, :] = spice.latrec(rad[i], lon[i], lat[i])

        # If system_dst was HPC or HPR, do conversion in RTN and apply correction.
        if system_dst in ['HPC', 'hpc']:
            system_dst = 'RTN'
            calc_hpc = True
        else:
            calc_hpc = False

        if system_dst in ['HPR', 'hpr']:
            system_dst = 'RTN'
            calc_hpr = True
        else:
            calc_hpr = False

        coord_dst_rec = self.convert_coord(dates,
                                           coord_src_rec,
                                           system_src,
                                           system_dst,
                                           observe_src=observe_src,
                                           observe_dst=observe_dst)

        # Convert back to latitude coords.
        if dates.isscalar:
            rad_dst, lon_dst, lat_dst = spice.reclat(coord_dst_rec)
        else:
            # Loop through state to do conversion (as spice.reclat doesn't handle arrays yet)
            rad_dst = np.zeros(dates.size, dtype=float)
            lon_dst = np.zeros(dates.size, dtype=float)
            lat_dst = np.zeros(dates.size, dtype=float)
            for i in range(dates.size):
                rad_dst[i], lon_dst[i], lat_dst[i] = spice.reclat(
                    coord_dst_rec[i, :])

        # Correct HPC coords if necessary
        if calc_hpc:
            lon_dst, lat_dst = self.convert_rtn_to_hpc(lon_dst,
                                                       lat_dst,
                                                       degrees=False)

        # Correct HPR coords if necessary
        if calc_hpr:
            lon_dst, lat_dst = self.convert_rtn_to_hpc(lon_dst,
                                                       lat_dst,
                                                       degrees=False)
            lon_dst, lat_dst = self.convert_hpc_to_hpr(lon_dst,
                                                       lat_dst,
                                                       degrees=False)

        # Correct Carrington longitudes if neccesary
        carrington_names = ['CARR', 'CARRINGTON', 'carr', 'carrington']
        if system_dst in carrington_names:
            if dates.size > 1:
                id_under = lon_dst < 0
                if any(id_under):
                    lon_dst[id_under] += 2 * np.pi

            elif dates.size == 1:
                if lon_dst < 0:
                    lon_dst += 2 * np.pi

        if degrees:
            lon_dst = np.rad2deg(lon_dst)
            lat_dst = np.rad2deg(lat_dst)

        # Bundle the output into one array.
        if dates.isscalar:
            coord_dst = np.array([rad_dst, lon_dst, lat_dst])
        else:
            rad_dst = np.expand_dims(rad_dst, axis=1)
            lon_dst = np.expand_dims(lon_dst, axis=1)
            lat_dst = np.expand_dims(lat_dst, axis=1)
            coord_dst = np.hstack((rad_dst, lon_dst, lat_dst))

        return coord_dst
Ejemplo n.º 38
0
    def get_fits_headers(self, *, start_time, average_time):

        try:
            et = spiceypy.scs2e(-144, str(average_time))
        except (SpiceBADPARTNUMBER, SpiceINVALIDSCLKSTRING):
            et = spiceypy.utc2et(average_time.isot)

        # HeliographicStonyhurst
        solo_sun_hg, sun_solo_lt = spiceypy.spkezr('SOLO', et, 'SUN_EARTH_CEQU', 'None', 'Sun')

        # Convert to spherical and add units
        hg_rad, hg_lon, hg_lat = spiceypy.reclat(solo_sun_hg[:3])
        hg_rad = hg_rad * u.km
        hg_lat, hg_lon = (hg_lat * u.rad).to('deg'), (hg_lon * u.rad).to('deg')
        # Calculate radial velocity add units
        rad_vel, *_ = spiceypy.reclat(solo_sun_hg[3:])
        rad_vel = rad_vel * (u.km / u.s)

        rsun_arc = np.arcsin((1 * u.R_sun) / hg_rad).decompose().to('arcsec')

        solo_sun_hee, _ = spiceypy.spkezr('SOLO', et, 'SOLO_HEE', 'None', 'Sun')
        solo_sun_hci, _ = spiceypy.spkezr('SOLO', et, 'SOLO_HCI', 'None', 'Sun')
        solo_sun_hae, _ = spiceypy.spkezr('SOLO', et, 'SUN_ARIES_ECL', 'None', 'Sun')
        solo_sun_heeq, _ = spiceypy.spkezr('SOLO', et, 'SOLO_HEEQ', 'None', 'Sun')
        solo_sun_gse, earth_solo_lt = spiceypy.spkezr('SOLO', et, 'EARTH_SUN_ECL', 'None', 'Earth')
        sun_earth_hee, sun_earth_lt = spiceypy.spkezr('Earth', et, 'SOLO_HEE', 'None', 'Sun')

        precision = 2
        headers = (
            ('SPICE_MK', self.meta_kernel_path.name, 'SPICE meta kernel file'),
            ('RSUN_ARC', rsun_arc.to_value('arcsec'),
             '[arcsec] Apparent photospheric solar radius'),
            # ('CAR_ROT', ,), Doesn't make sense as we don't have a crpix
            ('HGLT_OBS', np.around(hg_lat.to_value('deg'), precision),
             '[deg] s/c heliographic latitude (B0 angle)'),
            ('HGLN_OBS', np.around(hg_lon.to_value('deg'), precision),
             '[deg] s/c heliographic longitude'),
            # Not mistake same values know by different terms
            ('CRLT_OBS', np.around(hg_lat.to_value('deg'), precision),
             '[deg] s/c Carrington latitude (B0 angle)'),
            ('CRLN_OBS', np.around(hg_lon.to_value('deg'), precision),
             '[deg] s/c Carrington longitude (L0 angle)'),
            ('DSUN_OBS', np.around(hg_rad.to_value('m'), precision),
             '[m] s/c distance from Sun'),
            ('HEEX_OBS', np.around((solo_sun_hee[0]*u.km).to_value('m'), precision),
             '[m] s/c Heliocentric Earth Ecliptic X'),
            ('HEEY_OBS', np.around((solo_sun_hee[1]*u.km).to_value('m'), precision),
             '[m] s/c Heliocentric Earth Ecliptic Y'),
            ('HEEZ_OBS', np.around((solo_sun_hee[2]*u.km).to_value('m'), precision),
             '[m] s/c Heliocentric Earth Ecliptic Z'),
            ('HCIX_OBS', np.around((solo_sun_hci[0]*u.km).to_value('m'), precision),
             '[m] s/c Heliocentric Inertial X'),
            ('HCIY_OBS', np.around((solo_sun_hci[1]*u.km).to_value('m'), precision),
             '[m] s/c Heliocentric Inertial Y'),
            ('HCIZ_OBS', np.around((solo_sun_hci[2]*u.km).to_value('m'), precision),
             '[m] s/c Heliocentric Inertial Z'),
            ('HCIX_VOB', np.around((solo_sun_hci[3]*(u.km/u.s)).to_value('m/s'), precision),
             '[m/s] s/c Heliocentric Inertial X Velocity'),
            ('HCIY_VOB', np.around((solo_sun_hci[4]*(u.km/u.s)).to_value('m/s'), precision),
             '[m/s] s/c Heliocentric Inertial Y Velocity'),
            ('HCIZ_VOB', np.around((solo_sun_hci[5]*(u.km/u.s)).to_value('m/s'), precision),
             '[m/s] s/c Heliocentric Inertial Z Velocity'),
            ('HAEX_OBS', np.around((solo_sun_hae[0]*u.km).to_value('m'), precision),
             '[m] s/c Heliocentric Aries Ecliptic X'),
            ('HAEY_OBS', np.around((solo_sun_hae[1]*u.km).to_value('m'), precision),
             '[m] s/c Heliocentric Aries Ecliptic Y'),
            ('HAEZ_OBS', np.around((solo_sun_hae[0]*u.km).to_value('m'), precision),
             '[m] s/c Heliocentric Aries Ecliptic Z'),
            ('HEQX_OBS', np.around((solo_sun_heeq[0]*u.km).to_value('m'), precision),
             '[m] s/c Heliocentric Earth Equatorial X'),
            ('HEQY_OBS', np.around((solo_sun_heeq[1]*u.km).to_value('m'), precision),
             '[m] s/c Heliocentric Earth Equatorial Y'),
            ('HEQZ_OBS', np.around((solo_sun_heeq[2]*u.km).to_value('m'), precision),
             '[m] s/c Heliocentric Earth Equatorial Z'),
            ('GSEX_OBS', np.around((solo_sun_gse[0]*u.km).to_value('m'), precision),
             '[m] s/c Geocentric Solar Ecliptic X'),
            ('GSEY_OBS', np.around((solo_sun_gse[1]*u.km).to_value('m'), precision),
             '[m] s/c Geocentric Solar Ecliptic Y'),
            ('GSEZ_OBS', np.around((solo_sun_gse[2]*u.km).to_value('m'), precision),
             '[m] s/c Geocentric Solar Ecliptic Y'),
            ('OBS_VR', np.around(rad_vel.to_value('m/s'), precision),
             '[m/s] Radial velocity of spacecraft relative to Sun'),
            ('EAR_TDEL', np.around(sun_earth_lt - sun_solo_lt, precision),
             '[s] Time(Sun to Earth) - Time(Sun to S/C)'),
            ('SUN_TIME', np.around(sun_solo_lt, precision),
             '[s] Time(Sun to s/c)'),
            ('DATE_EAR', (start_time + np.around((sun_earth_lt - sun_solo_lt), precision)*u.s).fits,
             'Start time of observation, corrected to Earth'),
            ('DATE_SUN', (start_time - np.around(sun_solo_lt, precision)*u.s).fits,
             'Start time of observation, corrected to Su'),
        )

        return headers