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
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]
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)
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
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
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)
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
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
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
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}')
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
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
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]
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
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)
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
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
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
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(
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")}')
def _get_coords(self): if not self.spoint_set: raise SPointNotSetError return Coords.fromtuple(spice.reclat(self.spoint))
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
def coords(self): if not self.spoint_set: raise SPointNotSetError return SurfaceCoords.fromtuple(spice.reclat(self.spoint))
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
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))
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)
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)
#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')
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
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
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
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