class constants(object): # acceleration of gravity at the equator g_equ = 9.780 * u.m / u.s / u.s # acceleration of gravity at the pole g_pol = 9.8321849378 * u.m / u.s / u.s # acceleration of gravity at 45deg lat g_45 = 9.8306 * u.m / u.s / u.s # acceleration of gravity [m/s/s] g = 9.81 * u.m / u.s / u.s # (mean) Earth angular velocity [rad/s] w_pla = (2 * np.pi) / 86164.0 * u.rad / u.s # Earth equatorial radius [m], from SPICE R_pla = sp.bodvrd("EARTH", "RADII", 3)[1][0] * u.km # shape of the Earth ellipsoid [m,m,m], from SPICE abc_pla = sp.bodvrd("EARTH", "RADII", 3)[1] * u.km # flatness coefficient (frmm SPICE) f_pla = (abc_pla[1] - abc_pla[2]) / abc_pla[1] # default meteoroid volumic mass [kg/m^3] rhometeor = 3000.0 * u.kg / (u.m * u.m * u.m) # altitude below which the Dark Flight is computed max_alt_DF = 40 * u.km # Dark Flight: integration step in altitude [m] dh = -100.0 * u.m # altitude below which the height above ground is computed (DrkFlgt) gnd_alt_thld = 5000.0 * u.m # default number of clones for the dark flight computation nclone = 10
def cassini_titan_altlatlon(tempdatetime): et = spice.datetime2et(tempdatetime) state, ltime = spice.spkezr('CASSINI', et, 'IAU_TITAN', 'NONE', 'TITAN') lon, lat, alt = spice.recpgr('TITAN', state[:3], spice.bodvrd('TITAN', 'RADII', 3)[1][0], 2.64e-4) return alt, lat * spice.dpr(), lon * spice.dpr()
def nearest_moon(dt, target='CASSINI'): """ :param dt: :param target: :return: """ et = spice.datetime2et(dt) moondict = {} NAIFIDS = range(601, 654, 1) for x in NAIFIDS: moon = spice.bodc2s(x) frame = 'IAU_' + moon.upper() observ = moon.upper() corrtn = 'NONE' state, ltime = spice.spkpos(target, et, frame, corrtn, observ) # TODO do full calculation of altitude lon, lat, alt = spice.recpgr( moon.upper(), state, spice.bodvrd(moon.upper(), 'RADII', 3)[1][0], 2.64e-4) moondict[moon] = alt sorteddict = OrderedDict(sorted(moondict.items(), key=lambda t: t[1])) return sorteddict
def GM(body): """ Use SPICE software to get the product of the gravitational constant and the mass of <body>. """ _, GM = spice.bodvrd(body, "GM", 1) return GM[0]*1e9 # convert km**2 to m**2
def n_body_equation(self, t, y): """ n体问题微分方程 y_dot = f(t, y) 见英文版教材P117 :self.PARAM t: 对应tdb时刻 :self.PARAM y: 对应时刻人造卫星的状态向量[x, y, z, vx, vy, vz] :return: y_dot """ y_dot = np.empty((6, )) y_dot[:3] = y[3:] r_mex = y[:3] r_central = spice.spkezr(self.PARAM["Central_Body"], t, self.PARAM["Ref_Frame"], 'None', self.PARAM["Ref_Body"])[0][:3] r_central_mex = r_mex - r_central GM_central = spice.bodvrd(self.PARAM["Central_Body"], "GM", 1)[1][0] a_central = -GM_central * r_central_mex / norm(r_central_mex)**3 perturbations = sum([ self.perturbation(name, r_mex, t) for name in self.PARAM["Perturbation_Bodies"] ]) y_dot[3:] = a_central + perturbations if self.PARAM["Radiation"]: radiation_pressure = self.solar_radiation_pressure(t, y) y_dot[3:] += radiation_pressure return y_dot # v, a
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 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 GM(body): """ Use SPICE software to get the product of the gravitational constant and the mass of <body>. Input is SPICE name, e.g. "SUN" or "MOON". """ _, GM = spice.bodvrd(body, "GM", 1) return GM[0] * 1e9 # convert km to m
def get_sun_sizes(utc_start, utc_end, step_size): """get sun angular size and time steps given start and end times""" #spice constants abcorr = "None" #tolerance = "1" #method = "Intercept: ellipsoid" #prec = 3 #shape = "Ellipsoid" #load spiceypy kernels os.chdir(KERNEL_DIRECTORY) sp.furnsh(KERNEL_DIRECTORY + os.sep + METAKERNEL_NAME) print(sp.tkvrsn("toolkit")) os.chdir(BASE_DIRECTORY) utctimestart = sp.str2et(utc_start) utctimeend = sp.str2et(utc_end) durationseconds = utctimeend - utctimestart nsteps = int(np.floor(durationseconds / step_size)) timesteps = np.arange(nsteps) * step_size + utctimestart ref = "J2000" observer = "-143" target = "SUN" #get TGO-SUN pos tgo2sunpos = [ sp.spkpos(target, time, ref, abcorr, observer)[0] for time in timesteps ] sunaxes = sp.bodvrd("SUN", "RADII", 3)[1][0] #get mars axis values return ([np.arctan((sunaxes*2.0)/np.linalg.norm(tgo2sunVector))*sp.dpr()*60.0 \ for tgo2sunVector in tgo2sunpos], timesteps)
def target_body_radii(self): """ Returns ------- : list<double> Radius of all three axis of the target body """ rad = spice.bodvrd(self.target_name, 'RADII', 3) return rad[1]
def fetch_radii(self, targets): targets = [self.fromName(t) for t in targets] radii = [10] * len(targets) for k, target in enumerate(targets): try: radii[k] = spiceypy.bodvrd(target, 'RADII', 3)[1][0] except spiceypy.utils.exceptions.SpiceKERNELVARNOTFOUND: pass return radii
def getNorthPoleAngle(target, position, C, B, camera): """ Get angle north pole of target makes with image y-axis, in radians. """ # get target spin axis # the last row of the matrix is the north pole vector, *per spice docs* # seems correct, as it's nearly 0,0,1 Bz = B[2] print 'Bz=north pole spin axis',Bz # get target radius, km nvalues, radii = spice.bodvrd(target, 'RADII', 3) targetRadiusEquator = (radii[0] + radii[1]) / 2 targetRadiusPoles = radii[2] targetRadius = sum(radii) / 3 # flatteningCoefficient = (targetRadiusEquator - targetRadiusPoles) / targetRadiusEquator # print 'target radius in km', targetRadius # get north pole location positionNP = position + targetRadius * Bz print 'positionNP=north pole in world coords', positionNP # get target position in camera space c = np.dot(C, position) cNP = np.dot(C, positionNP) print 'c=position in camera space',c print 'cNP=north pole in camera space',cNP # get camera fov and focal length fovDegrees = config.cameraFOVs[camera] # 0.424 or 3.169 deg fovRadians = fovDegrees * math.pi / 180 f = 1.0 / math.tan(fovRadians/2) # focal length (relative to screen halfwidth of 1.0) print 'f=focal length',f # get camera-to-screen matrix S cz = c[2] fz = f/cz # print 'fz=f/cz',fz S = np.array([[fz,0,0],[0,fz,0]]) # get screen coordinate (-1 to 1, -1 to 1) s = np.dot(S, c) sNP = np.dot(S, cNP) # ie sx=cx*f/cz; sy=cy*f/cz print 's=screen space (-1 to 1)',s print 'sNP=screen space north pole (-1 to 1)',sNP # get angle between north pole and image y-axis npDelta = sNP-s npRadians = math.atan(npDelta[0]/npDelta[1]) npAngle = npRadians * 180/math.pi print 'npAngle',npAngle return npRadians
def producegeometrylamda(et, sv, when): [TGO, _] = spice.spkpos(sv.front, et - when, sv.fframe, 'NONE', sv.target) [MEX, _] = spice.spkpos(sv.front, et - when, sv.fframe, 'NONE', sv.obs) dist = math.floor(spice.vdist(TGO, MEX)) print(dist) # NEED TO PRODUCE VECTOR OF SZA AND HEIGHTS THAT ARE 'DIST' LONG [13242.9 m] *comp expensive # start by making DIST length vector, 3 height, for every meter from mex to tgo # MAYBE FIND THE UNIT VECTOR AND ADD ONE IN ITS DIRECTION!! angleseparation = (spice.vsep(MEX, TGO)) * (180 / math.pi ) # angle taken a mars center initialangle = (spice.vsep(-MEX, (TGO - MEX))) * ( 180 / math.pi ) # angle taken at mars-MEX-tgo, that points to tgo. needed for the bending functions original starting angle #script needs to work via periods of ray and not meters. [totalperiods is the main iterable, not meters] vacuumwavelength = constants.c / 437.1e6 scale = 1 # scale =10, means we are itertating per 100 wavelenghts instead of 1000 (default 1000 because SPICE works in km) wavelengthsinameter = 1 / vacuumwavelength a = wavelengthsinameter * dist * scale total1000periods = math.floor(a) # ~that many thousands of periods remainingdistance = (vacuumwavelength / scale) * ( (wavelengthsinameter * dist * scale) - total1000periods ) # quanitfy the remaineder, this distance can # added later, this remaining portion is extreamly high altitude (near Target) and has no refractive effects. therfor simply added (km) #total1000periods = total1000periods.astype(int) sc2sc = TGO - MEX norm = np.linalg.norm(sc2sc) unitsc2sc = sc2sc / (norm * vacuumwavelength * scale ) #this needs to shrink if the repeatable expands points = np.empty([3, total1000periods]) sza = np.empty([1, total1000periods]) marsrad = spice.bodvrd(sv.front, 'RADII', 3) flatteningcoefficient = (marsrad[1][0] - marsrad[1][2]) / marsrad[1][0] equatorialradii = marsrad[1][0] # find direction of sun, it will not change much during the occultation. so only calc it once [SUN, _] = spice.spkpos(sv.front, et, sv.fframe, 'NONE', 'SUN') for i in range(total1000periods): point = MEX + ( i * unitsc2sc ) #move along ray, 1000 wavelength distance at a time (685 m). but unitsc2sc is in km... sza[0, i] = spice.vsep(SUN, point) points[:, i] = spice.recgeo(point, equatorialradii, flatteningcoefficient) points[0, i] = (points[0, i] * (-180 / math.pi)) points[1, i] = (points[1, i] * (-180 / math.pi)) print((i / math.floor(total1000periods)) * 100) ray = np.concatenate((points, sza), axis=0) print('stop here') return ray, dist, angleseparation, initialangle, total1000periods, vacuumwavelength, remainingdistance
def producegeometrymeter(et, sv, when): [TGO, _] = spice.spkpos(sv.front, et - when, sv.fframe, 'NONE', sv.target) [MEX, _] = spice.spkpos(sv.front, et - when, sv.fframe, 'NONE', sv.obs) dist = math.floor(spice.vdist(TGO, MEX)) print(dist) # NEED TO PRODUCE VECTOR OF SZA AND HEIGHTS THAT ARE 'DIST' LONG [13242.9 m] *comp expensive # start by making DIST length vector, 3 height, for every meter from mex to tgo # MAYBE FIND THE UNIT VECTOR AND ADD ONE IN ITS DIRECTION!! angleseparation = (spice.vsep(MEX, TGO)) # angle taken a mars center initialangle = (spice.vsep(-MEX, (TGO - MEX))) * ( 180 / math.pi ) # angle taken at mars-MEX-tgo, that points to tgo. needed for the bending functions original starting angle #script needs to work via periods of ray and not meters. [totalperiods is the main iterable, not meters] scale = 0.1 # scale =10, means we are itertating per 100 wavelenghts instead of 1000 (default 1000 because SPICE works in km) dist = math.floor(dist) # km sc2sc = TGO - MEX norm = np.linalg.norm(sc2sc) unitsc2sc = sc2sc / norm #this needs to shrink if the repeatable expands points = np.empty([3, dist]) sza = np.empty([1, dist]) angleprogression = np.empty([1, dist]) xyzpoints = np.zeros([3, dist]) marsrad = spice.bodvrd(sv.front, 'RADII', 3) flatteningcoefficient = (marsrad[1][0] - marsrad[1][2]) / marsrad[1][0] equatorialradii = marsrad[1][0] # find direction of sun, it will not change much during the occultation. so only calc it once [SUN, _] = spice.spkpos(sv.front, et, sv.fframe, 'NONE', 'SUN') for i in range(dist): xyzpoint = MEX + ( i * unitsc2sc ) #move along ray, 1000 wavelength distance at a time (685 m). but unitsc2sc is in km... xyzpoints[:, i] = xyzpoint sza[0, i] = spice.vsep(SUN, xyzpoint) angleprogression[0, i] = (spice.vsep(xyzpoint, MEX)) * (180 / math.pi) points[:, i] = spice.recgeo(xyzpoint, equatorialradii, flatteningcoefficient) points[0, i] = (points[0, i] * (-180 / math.pi)) points[1, i] = (points[1, i] * (-180 / math.pi)) print((i / math.floor(dist)) * 100) ray = np.concatenate((points, sza), axis=0) #plt.plot(angleprogression[0,:], ray[2,:]) #plt.show() # ray is in lat/lon/alt + sza and xyzpoints is cartesian, both describe the same thing return ray, dist, unitsc2sc, angleseparation, initialangle, MEX, TGO, xyzpoints, angleprogression
def target_body_radii(self): """ Returns a list containing the radii of the target body Expects target_name to be defined. This must be a string containing the name of the target body Returns ------- : list<double> Radius of all three axis of the target body """ rad = spice.bodvrd(self.target_name, 'RADII', 3) return rad[1]
def cassini_altlatlon(utc, target='TITAN', output=False): state = cassini_phase(utc) lon, lat, alt = spice.recpgr(target, state[:3], spice.bodvrd(target, 'RADII', 3)[1][0], 2.64e-4) if output: print("ALtitude", alt) print("Latitude", lat * spice.dpr()) print("Longtitude", lon * spice.dpr()) return alt, lat * spice.dpr(), lon * spice.dpr()
def body_shadow_function(self, r_mex, name, t): """ 计算阴影函数(shadow function)见英文教材P81 3.4.2 :self.PARAM r_mex: 人造卫星(MEX)的位置向量 :self.PARAM name: 遮挡天体的名称 :self.PARAM t: TDB时刻 :return: 阴影函数v """ r_body = spice.spkezr(name, t, self.PARAM["Ref_Frame"], 'None', self.PARAM["Ref_Body"])[0][:3] r_sun = spice.spkezr("Sun", t, self.PARAM["Ref_Frame"], 'None', self.PARAM["Ref_Body"])[0][:3] r_body_mex = r_mex - r_body r_mex_sun = r_sun - r_mex R_body = spice.bodvrd(name, "RADII", 3)[1][0] RS = spice.bodvrd("Sun", "RADII", 3)[1][0] a = asin(RS / norm(r_mex_sun)) b = asin(R_body / norm(r_body_mex)) c = self.agl_between(-1 * r_body_mex, r_mex_sun) v = self.shadow_function(a, b, c) return v
def orbital_elements(date, observer, target, frame='J2000', abcorr='LT+S'): state = get_state(date, observer, target, frame, abcorr) et = spiceypy.str2et(date) mu = spiceypy.bodvrd(observer, 'GM', 1)[1][0] # # Compute the orbital elements # elements = spiceypy.oscltx(state, et, mu) return elements
def producegeometrymeter(et, sv, when): [TGO, _] = spice.spkpos(sv.front, et - when, sv.fframe, 'NONE', sv.target) [MEX, _] = spice.spkpos(sv.front, et - when, sv.fframe, 'NONE', sv.obs) dist = math.floor(spice.vdist(TGO, MEX)) angleseparation = (spice.vsep(MEX, TGO)) # angle taken a mars center initialangle = (spice.vsep(-MEX, (TGO - MEX))) * ( 180 / math.pi ) # angle taken at mars-MEX-tgo, that points to tgo. needed for the bending functions original starting angle #script needs to work via periods of ray and not meters. [totalperiods is the main iterable, not meters] sc2sc = TGO - MEX norm = np.linalg.norm(sc2sc) unitsc2sc = sc2sc / norm #this needs to shrink if the repeatable expands points = np.empty([3, dist]) sza = np.empty([1, dist]) angleprogression = np.empty([1, dist]) xyzpoints = np.zeros([3, dist]) marsrad = spice.bodvrd(sv.front, 'RADII', 3) flatteningcoefficient = (marsrad[1][0] - marsrad[1][2]) / marsrad[1][0] equatorialradii = marsrad[1][0] # find direction of sun, it will not change much during the occultation. so only calc it once [SUN, _] = spice.spkpos(sv.front, et, sv.fframe, 'NONE', 'SUN') for i in range(dist): xyzpoint = MEX + ( i * unitsc2sc ) #move along ray, 1000 wavelength distance at a time (685 m). but unitsc2sc is in km... xyzpoints[:, i] = xyzpoint sza[0, i] = spice.vsep(SUN, xyzpoint) angleprogression[0, i] = (spice.vsep(xyzpoint, MEX)) * (180 / math.pi) points[:, i] = spice.recgeo(xyzpoint, equatorialradii, flatteningcoefficient) points[0, i] = (points[0, i] * (-180 / math.pi)) points[1, i] = (points[1, i] * (-180 / math.pi)) ray = np.concatenate((points, sza), axis=0) # important for when sza is included #plt.plot(angleprogression[0,:], ray[2,:]) #plt.show() # ray is in lat/lon/alt + sza and xyzpoints is cartesian, both describe the same thing return initialangle, MEX, TGO, xyzpoints
def TangentPointAltitude(time): epoch = 636491202.20059 target = '-143' obs = '-41' alt = np.zeros(len(time) + 1) for i in time: [tgo, _] = spice.spkpos('MARS', epoch - i, 'IAU_MARS', 'NONE', target) [mex, _] = spice.spkpos('MARS', epoch - i, 'IAU_MARS', 'NONE', obs) [states, _] = spice.spkezr(target, epoch - i, 'IAU_MARS', 'NONE', obs) sc2scvector = states[0:3] displacement = np.linalg.norm(sc2scvector) sc2scunitvector = np.true_divide(sc2scvector, displacement) marsrad = spice.bodvrd('MARS', 'RADII', 3) _, alt[i] = spice.npedln(marsrad[1][0], marsrad[1][1], marsrad[1][2], tgo, sc2scunitvector) return alt * 1000
def get_radius(bod_id): """ aether-rest-server.py -- get_radius This function simply gets the radii of the body specified by bod_id. Params: bod_id <str> -- the NAIF ID of the body for which min and max speeds shall be obtained. Optionally, a valid body name may be passed instead. Returns: list[<float>, <float>, <float>] -- TODO """ try: # get radii from SPICE return spice.bodvrd(bod_id, "RADII", 3)[1].tolist() # This should never be hit, since this function is only called on bodies which have radius data, but just in case... except: return "NO RADIUS DATA AVAILABLE"
def get_mass(bod_id): """ aether-rest-server.py -- get_mass This function simply gets the mass in kg of the body specified by bod_id. Params: bod_id <str> -- the NAIF ID of the body for which min and max speeds shall be obtained. Optionally, a valid body name may be passed instead. Returns: <float> the mass of the body in kg """ # the gravitational constant G = 6.67430e-11 try: # return mass from SPICE -- G is divided so the result is in kg return float(spice.bodvrd(bod_id, "GM", 1)[1][0] / (G / 1000000000)) # This should never be hit, since this function is only called on bodies which have mass data, but just in case... except: return "NO MASS DATA AVAILABLE"
def cassini_altitude(dt, moon, target='CASSINI'): """ :param dt: :param moon: :param target: :return: """ et = spice.datetime2et(dt) frame = 'IAU_' + moon.upper() observ = moon.upper() corrtn = 'NONE' state, ltime = spice.spkpos(target, et, frame, corrtn, observ) # TODO do full calculation of altitude lon, lat, alt = spice.recpgr(moon.upper(), state, spice.bodvrd(moon.upper(), 'RADII', 3)[1][0], 2.64e-4) return alt
def perturbation(name, r_mex, t): """ 计算摄动天体加速度 :param name: 摄动天体名称 :param r_mex: 人造卫星(MEX)的位置向量 :param t: TDB时刻 :return: 对应天体摄动加速度加速度[ax, ay, az] """ r_body = spice.spkezr(name, t, PARAM["Ref_Frame"], 'None', PARAM["Ref_Body"])[0][:3] r_cent = spice.spkezr(PARAM["Central_Body"], t, PARAM["Ref_Frame"], 'None', PARAM["Ref_Body"])[0][:3] GM_body = spice.bodvrd(name, "GM", 1)[1][0] r_body_mex = r_mex - r_body r_cent_body = r_body - r_cent a = -GM_body * (r_cent_body / norm(r_cent_body)**3 + r_body_mex / norm(r_body_mex)**3) return a
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 R(self): if self._R is None: results = spice.bodvrd(self.name, "RADII", 3) self._R = np.mean(results[1]) * u.km return self._R
def get_isd(label): """ TODO: This function (and all like it) needs to open with some robust method to make sure this is in fact an MDIS label. """ instrument_name = { 'MDIS-NAC': 'MSGR_MDIS_NAC', 'MERCURY DUAL IMAGING SYSTEM NARROW ANGLE CAMERA': 'MSGR_MDIS_NAC', 'MERCURY DUAL IMAGING SYSTEM WIDE ANGLE CAMERA': 'MSGR_MDIS_WAC' } metakernel_dir = config.mdis mks = sorted(glob(os.path.join(metakernel_dir, '*.tm'))) instrument_id = instrument_name[label['INSTRUMENT_ID']] spacecraft_name = label['MISSION_NAME'] target_name = label['TARGET_NAME'] time = label['START_TIME'] messenger_mk = None for mk in mks: if str(time.year) in os.path.basename(mk): messenger_mk = mk spice.furnsh(messenger_mk) # Spice likes ids over names, so grab the ids from the names spacecraft_id = spice.bods2c(spacecraft_name) ikid = spice.bods2c(instrument_id) # Load the instrument and target metadata into the ISD reference_frame = 'IAU_{}'.format(target_name) isd = {} rad = spice.bodvrd(target_name, 'RADII', 3) isd['radii'] = {} isd['radii']['semimajor'] = rad[1][0] isd['radii']['semiminor'] = rad[1][1] isd['optical_distortion'] = {} odk_mssgr_x = spice.gdpool('INS{}_OD_T_X'.format(ikid), 0, 10) odk_mssgr_y = spice.gdpool('INS{}_OD_T_Y'.format(ikid), 0, 10) isd['optical_distortion']['x'] = list(odk_mssgr_x) isd['optical_distortion']['y'] = list(odk_mssgr_y) isd['focal2pixel_samples'] = list( spice.gdpool('INS{}_TRANSX'.format(ikid), 0, 3)) isd['focal2pixel_lines'] = list( spice.gdpool('INS{}_TRANSY'.format(ikid), 0, 3)) # Load information from the IK kernel isd['focal_length_model'] = {} focal_legnth_coeffs = spice.gdpool('INS{}_FL_TEMP_COEFFS '.format(ikid), 0, 5) isd['focal_length_model']['focal_length'] = focal_length_from_temp( label['FOCAL_PLANE_TEMPERATURE'].value, focal_legnth_coeffs) isd['focal_length_model']['focal_length_epsilon'] = float( spice.gdpool('INS{}_FL_UNCERTAINTY'.format(ikid), 0, 1)[0]) isd['image_lines'] = int( spice.gipool('INS{}_PIXEL_LINES'.format(ikid), 0, 1)[0]) isd['image_samples'] = int( spice.gipool('INS{}_PIXEL_SAMPLES'.format(ikid), 0, 1)[0]) isd['starting_detector_sample'] = int( spice.gdpool('INS{}_FPUBIN_START_SAMPLE'.format(ikid), 0, 1)[0]) isd['starting_detector_line'] = int( spice.gdpool('INS{}_FPUBIN_START_LINE'.format(ikid), 0, 1)[0]) # Now time sclock = label['SPACECRAFT_CLOCK_START_COUNT'] exposure_duration = label['EXPOSURE_DURATION'].value exposure_duration = exposure_duration * 0.001 # Scale to seconds # Get the instrument id, and, since this is a framer, set the time to the middle of the exposure start_et = spice.scs2e(spacecraft_id, sclock) start_et += (exposure_duration / 2.0) end_et = spice.scs2e( spacecraft_id, label['SPACECRAFT_CLOCK_STOP_COUNT']) + (exposure_duration / 2.0) del_et = end_et - start_et et = (start_et + end_et) / 2 isd['starting_ephemeris_time'] = start_et isd['dt_ephemeris'] = del_et isd['number_of_ephemerides'] = 1 isd['interpolation_method'] = 'lagrange' isd['center_ephemeris_time'] = et # Get the rotation angles from MDIS NAC frame to Mercury body-fixed frame camera2bodyfixed = spice.pxform(instrument_id, reference_frame, et) quat = spice.m2q(camera2bodyfixed) isd['sensor_orientation'] = list(quat) # Get the Sensor Position loc, _ = spice.spkpos(target_name, et, reference_frame, 'LT+S', spacecraft_name) isd['sensor_location'] = {} isd['sensor_location']['x'] = loc[0] isd['sensor_location']['y'] = loc[1] isd['sensor_location']['z'] = loc[2] isd['sensor_location']['unit'] = 'm' # Get the velocity v_state, lt = spice.spkezr(spacecraft_name, et, reference_frame, 'NONE', target_name) isd['sensor_velocity'] = {} isd['sensor_velocity']['x'] = v_state[3] isd['sensor_velocity']['y'] = v_state[4] isd['sensor_velocity']['z'] = v_state[5] isd['sensor_velocity']['unit'] = 'm' isd['reference_height'] = {} isd['reference_height']['minheight'] = label.get('min_valid_height', -8000) isd['reference_height']['maxheight'] = label.get('max_valid_height', 8000) isd['reference_height']['unit'] = 'KM' # Get the sun position sun_state, lt = spice.spkezr("SUN", et, reference_frame, 'NONE', target_name) # Get the sun position, convert to meters xpos, ypos, zpos = [e.value for e in label['SC_SUN_POSITION_VECTOR']] xvel, yvel, zvel = [e.value for e in label['SC_SUN_VELOCITY_VECTOR']] # lighttime should always be off isd['sun_position'] = {} isd['sun_position']['x'] = sun_state[0] isd['sun_position']['y'] = sun_state[1] isd['sun_position']['z'] = sun_state[2] isd['sun_velocity'] = {} isd['sun_velocity']['x'] = sun_state[3] isd['sun_velocity']['y'] = sun_state[4] isd['sun_velocity']['z'] = sun_state[5] return isd
def EarthRepeatOrbits(jk, e, Variable, VarType, isHighFidelity=False, printStatus=False): """Find a set of repeating ground track orbits around Earth (Earth-repeat orbits).""" # importing the required modules import spiceypy as spice import math import numpy as np Result = 0 Nsolutions = 0 # Error handling jkSize = jk.shape if (int(jkSize[1]) != 2): print( 'Incorrect matrix size for jk matrix. Only two coloumns are required.' ) return Result elif (VarType != 'Alti') and (VarType != 'Inclin'): print( 'Inrecognized input for the argument specifying the variable type.' ) return Result elif e >= 1.0 or e < 0: print( 'Only circular or elliptical orbits are possible. Check the value of eccentricity.' ) return Result elif (Variable[1] - Variable[0]) % Variable[2] != 0: print( 'Integer number of steps are not possible. Check inputs for the argument - "Variable".' ) return Result # Extracting the parameters spice.furnsh("./External_files/Spice_kernels/kernel_load.txt") muE = spice.bodvrd('Earth', 'GM', 1) mu = muE[1][0] # [km3/s2] for earth J2 = 1082.63E-6 #J2 for earth RE = spice.bodvrd('EARTH', 'RADII', 3) Re = RE[1][0] # [km], Average radius of Earth De = 86164.1004 # [s], Sidereal day k2 = 0.75 * J2 * math.sqrt(mu) * Re * Re r2d = 1 / spice.rpd() # Radian to degree conversion # Creating storage steps = int((Variable[1] - Variable[0]) / Variable[2]) + 1 Result = np.zeros(int(jkSize[0]) * int(steps) * 6) Result.shape = [int(jkSize[0]), int(steps), 6] # Computations for count in range(0, jkSize[0]): #looping over j and k values for rowCount in range(0, int(steps)): #looping over variable values j = jk[count, 0] k = jk[count, 1] # Extracting the value of variable var = Variable[0] + rowCount * Variable[2] # Storing values known so far Result[count, rowCount, 0] = j Result[count, rowCount, 1] = k Result[count, rowCount, 2] = e Result[count, rowCount, 3] = var Result[count, rowCount, 4] = math.nan # to be computed Result[count, rowCount, 5] = math.nan # to be computed if isHighFidelity == False: if VarType == 'Alti': a = (Re + var) / (1 - e) T = 2 * math.pi * math.sqrt(a**3 / mu) DeltaL1 = -2 * math.pi * (T / De) # DeltaL2 = C1 * cosi C1 = (-3 * math.pi * J2 * Re**2) / (a**2 * (1 - e**2)**2) C2 = -2 * math.pi * k / j - DeltaL1 C3 = 2 * math.pi * k / j - DeltaL1 # Inverse cosine if (C2 < 0) & (abs(C2 / C1) <= 1): DeltaL2 = C2 i = math.acos( DeltaL2 / C1) * r2d # for i = [0, 90] deg Result[count, rowCount, 4] = i Nsolutions += 1 elif (C2 > 0) & (abs(C2 / C1) <= 1): DeltaL2 = C2 i = math.acos( DeltaL2 / C1) * r2d # for i = (90, 180] deg Result[count, rowCount, 4] = i Nsolutions += 1 elif (C3 < 0) & (abs(C3 / C1) <= 1): DeltaL2 = C3 i = math.acos( DeltaL2 / C1) * r2d # for i = [0, 90] deg Result[count, rowCount, 5] = i Nsolutions += 1 elif (C3 > 0) & (abs(C3 / C1) <= 1): DeltaL2 = C3 i = math.acos( DeltaL2 / C1) * r2d # for i = (90, 180] deg Result[count, rowCount, 5] = i Nsolutions += 1 elif VarType == 'Inclin': print( 'Function is not defined for low fidelity + unknown inclination case.' ) return Result else: if VarType == 'Alti': a = (Re + var) / (1 - e) C1 = -2 * k2 * (a**-3.5) * ( (1 - e**2)**-2) * De * r2d # RAAN_dot = C1 * cos(i) C2 = k2 * (a**-3.5) * ( (1 - e**2)** -2) * De * r2d # omega_dot = C2 *(5*(cosd(i))^2 -1) C3 = k2 * (a**-3.5) * ( (1 - e**2)** -1.5) * De * r2d # M_dot = C3 *(3*(cosd(i))^2 -1) n = De * r2d * math.sqrt(mu / a**3) # For the quadratic equation A*x^2 + B*x +C = 0 with x = cos(i) A = 5 * C2 + 3 * C3 B = C1 * j / k C = n - 360 * j / k - (C2 + C3) D = B**2 - 4 * A * C if D >= 0: if (D == 0) & (abs(-B / (2 * A)) <= 1): i1 = math.acos(-B / (2 * A)) Result[count, rowCount, 4] = i1 * r2d Nsolutions += 1 elif (D > 0): x1 = (-B + math.sqrt(D)) / (2 * A) x2 = (-B - math.sqrt(D)) / (2 * A) if abs(x1) <= 1: i1 = math.acos(x1) Result[count, rowCount, 4] = i1 * r2d Nsolutions += 1 if abs(x2) <= 1: i2 = math.acos(x2) Result[count, rowCount, 5] = i2 * r2d Nsolutions += 1 elif VarType == 'Inclin': a0 = math.pow( (mu * De**2 * k**2 / (4 * math.pi**2 * j**2)), 1 / 3) iterations = 0 i = var L_dot = 360 while True: iterations = iterations + 1 RAAN_dot = -2 * k2 * math.pow(a0, -3.5) * math.cos( i / r2d) * math.pow(1 - e**2, -2) * De * r2d omega_dot = k2 * math.pow( a0, -3.5) * (5 * (math.cos(i / r2d))**2 - 1) * math.pow(1 - e**2, -2) * De * r2d M_dot = k2 * math.pow(a0, -3.5) * ( 3 * (math.cos(i / r2d))**2 - 1) * math.pow( 1 - e**2, -1.5) * De * r2d n = (j / k) * (L_dot - RAAN_dot) - (omega_dot + M_dot) a1 = (mu / (n / (De * r2d))**2)**(1 / 3) if iterations > 1000: a0 = a1 break elif (abs(a1 - a0) < 10**(-10)): a0 = a1 break else: a0 = a1 # Only tracking the feasible solutions i.e. positive altitudes if ((a0 * (1 - e)) - Re) > 0: Nsolutions += 1 Result[count, rowCount, 4] = (a0 * (1 - e)) - Re # Printing the status of solutions obtained if printStatus == True: print(Nsolutions, ' solutions are obtained for the Earth-repeat orbits.') return Result
def geometry(et, bsight, target, frame, sensor, observer=''): if not observer: observer = sensor # Time tag [UTC] # pixel id [(x,y)] # corner id [(x,y)] # Requested geometry # lat lon intersection (planetocentric) # lat lon subspacecraft # lat lon subsolar # target distance intersection # target angular diameter # local solar time intersection # phase angle intersection # emission angle intersection # incidence angle intersection # # We retrieve the camera information using GETFOV. More info available: # # https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/getfov_c.html # sensor_id = spiceypy.bodn2c(sensor) (shape, sensor_frame, ibsight, vectors, bounds) = spiceypy.getfov(sensor_id, 100) visible = spiceypy.fovtrg(sensor, target, 'ELLIPSOID', frame, 'LT+S', observer, et) if not visible: return 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 tarid = spiceypy.bodn2c(target) n, radii = spiceypy.bodvrd(target, 'RADII', 3) re = radii[0] rp = radii[2] f = (re - rp) / re try: # # https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/sincpt_c.html # # For each pixel we compute the possible intersection with the target, if # the target is intersected we then compute the illumination angles. We # use the following SPICE APIs: SINCPT and ILLUMF # # https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/sincpt_c.html # https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/illumf_c.html # (spoint, trgepc, srfvec) = \ spiceypy.sincpt('ELLIPSOID', target, et, frame, 'LT+S', observer, sensor_frame, bsight) (tarlon, tarlat, taralt) = spiceypy.recgeo(spoint, re, f) tardis = spiceypy.vnorm(srfvec) # # Angular diameter # tarang = np.degrees( 2 * np.arctan(max(radii) / spiceypy.vnorm(spoint + srfvec))) # # https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/illumf_c.html # (trgenpc, srfvec, phase, incdnc, emissn, visiblef, iluminatedf) = \ spiceypy.illumf('ELLIPSOID', target, 'SUN', et, frame, 'LT+S', observer, spoint) phase *= spiceypy.dpr() incdnc *= spiceypy.dpr() emissn *= spiceypy.dpr() # # https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/et2lst_c.html # # VARIABLE I/O DESCRIPTION # -------- --- -------------------------------------------------- # et I Epoch in seconds past J2000 epoch. # body I ID-code of the body of interest. # lon I Longitude of surface point (RADIANS). # type I Type of longitude "PLANETOCENTRIC", etc. # timlen I Available room in output time string. # ampmlen I Available room in output `ampm' string. # hr O Local hour on a "24 hour" clock. # mn O Minutes past the hour. # sc O Seconds past the minute. # time O String giving local time on 24 hour clock. # ampm O String giving time on A.M./ P.M. scale. (hr, mn, sc, ltime, ampm) = \ spiceypy.et2lst(et, tarid, tarlon, 'PLANETOCENTRIC', 80, 80) # # https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/subpnt_c.html # # Variable I/O Description # -------- --- -------------------------------------------------- # method I Computation method. # target I Name of target body. # et I Epoch in TDB seconds past J2000 TDB. # fixref I Body-fixed, body-centered target body frame. # abcorr I Aberration correction flag. # obsrvr I Name of observing body. # spoint O Sub-observer point on the target body. # trgepc O Sub-observer point epoch. # srfvec O Vector from observer to sub-observer point # (spoint, trgepc, srfev) = \ spiceypy.subpnt('INTERCEPT/ELLIPSOID', target, et, frame, 'LT+S', observer) (sublon, sublat, subalt) = spiceypy.recgeo(spoint, re, f) # # https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/subslr_c.html # # Variable I/O Description # -------- --- -------------------------------------------------- # method I Computation method. # target I Name of target body. # et I Epoch in ephemeris seconds past J2000 TDB. # fixref I Body-fixed, body-centered target body frame. # abcorr I Aberration correction. # obsrvr I Name of observing body. # spoint O Sub-solar point on the target body. # trgepc O Sub-solar point epoch. # srfvec O Vector from observer to sub-solar point. # (spoint, trgepc, srfev) = \ spiceypy.subslr('INTERCEPT/ELLIPSOID', target, et, frame, 'LT+S', observer) (sunlon, sunlat, sunalt) = spiceypy.recgeo(spoint, re, f) return tarlon, tarlat, sublon, sublat, sunlon, sunlat, tardis, tarang, ltime, phase, emissn, incdnc except: return 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
def _get_radii(self): _, radii = spice.bodvrd(self.target, "RADII", 3) return Radii(*radii)
def flatbending(xyzpoints, initialangle, MEX, TGO, referencedirection): class SpiceVariables: obs = '-74' # NAIF code for MEX target = 'MARS ODYSSEY' # NAIF code for TGO ['EARTH'/'SUN'/ a groundstation etc] obsfrm = 'IAU_MARS' abcorr = 'NONE' crdsys = 'LATITUDINAL' coord = 'LATITUDE' stepsz = 100.0 # Check every 300 seconds if there is an occultation MAXILV = 100000 #Max number of occultations that can be returned by gfoclt bshape = 'POINT' fshape = 'DSK/UNPRIORITIZED' front = 'MARS' fframe = 'IAU_MARS' TFMT = 'YYYY-MM-DD HR:MN:SC' # Format that Cosmographia understands sv = SpiceVariables() #form a coordinate system where tgo is @ y=0 and x= (5000 +norm), Mar's Barrycenter being @ [5000,0] subgroupsize = 1 #initialise non-global variables miniray = np.zeros(subgroupsize) raystep = np.zeros( (2, 100000000)) # create a large array to populate and then shrink later barry2mex = np.linalg.norm(MEX) barry2tgo = np.linalg.norm(TGO) #find the martian geomoerty so you can reliably find the altitude of a point marsrad = spice.bodvrd(sv.front, 'RADII', 3) flatteningcoefficient = (marsrad[1][0] - marsrad[1][2]) / marsrad[1][0] equatorialradii = marsrad[1][0] TGO = TGO + 0 MEX = MEX + 0 #force to be non-strided _, _, MEXalt = spice.recgeo(MEX, equatorialradii, flatteningcoefficient) _, _, TGOalt = spice.recgeo(TGO, equatorialradii, flatteningcoefficient) #the possition of MEX is found by assuming that it will be somewhere over the relative horizon from TGO # (meaning over θ = 90°), finding the angle between the MEX and TGO's negative vector, will give the coords of MEX MexRelativeElevation = spice.vsep(-TGO, MEX) #radians mex_y = barry2mex * np.sin(MexRelativeElevation) mex_x = barry2mex * np.cos(MexRelativeElevation) mex = np.array([0 - mex_x, mex_y]) tgo = np.array([0 + barry2tgo, 0]) barry = np.array([0, 0]) #to plot the non-refracted propogation, we must convert the 3d xyzpoints to 2d, we do this the same way we found the x&y for MEX # ,using the norm distance and sep from -TGO5 length = np.size(xyzpoints, 1) UnrefractedDistance = np.linalg.norm(xyzpoints[:, 0] - xyzpoints[:, -1]) #in km UnrefractedRay = np.zeros([2, length]) for i in range(length): #conversion to 2D point = xyzpoints[:, i] + 0 #need to put vector into temp variable as spice cant handle strided array inputs angle = spice.vsep(-TGO, point) norm = np.linalg.norm(point) point_x = norm * np.cos(angle) point_y = norm * np.sin(angle) UnrefractedRay[0, i] = 0 - point_x UnrefractedRay[1, i] = point_y #this will produce and angle that is likly not going to be exactly on #the original propagation path, you compare to this if there is a drifting error, as both this and the resultant refracted ray # have the same bias error. THIS ANGLE IS BENDING ANTICLOCKWISE IN THIS FRAME (BENDING UPWARDS) initialtheta = -(spice.vsep(MEX - TGO, MEX)) nicetohave = np.degrees(initialtheta) #THIS NEEDS TO VARY IF THERE IS AN OVERSHOOT unit = 1 # in km unitoriginal = unit rotationvector = np.array(((np.cos(initialtheta), -np.sin(initialtheta)), (np.sin(initialtheta), np.cos(initialtheta)))) #get unit vecotr of -MEX (then add this vecotr to MEX for each alt calcultation) unitmex = -mex / barry2mex #unit direction (2d) if referencedirection == [0, 0, 0]: initialdirection = unitmex.dot( rotationvector ) * unit #make a 2d vector coming from MEX YOU DO NOT KNOW WHAT WAY THIS IS ROTATING else: # if there is a value for the fed-back starting direction than use this as the first firection initialdirection = referencedirection iterationcount = 0 #while iterationcount<100: #print( "Finding Bending Angle (", str(iterationcount) ,"% Complete)") errorstore = np.zeros((11, 100000)) S = np.zeros(20000) #IF REFERCEDRECTION==0 DO NORMAL, IF /= INCLUDE THIS AS THE FIRST DIRECTION. while iterationcount < 10: tic = timer.perf_counter() if iterationcount == 0: direction = initialdirection else: missangle = missangle / 1 missrotationvector = np.array( ((np.cos(missangle), -np.sin(missangle)), (np.sin(missangle), np.cos(missangle)))) direction = initialdirection.dot(missrotationvector) #check the differecne between the initial and the direction, see if the same for both CHECK_ME = direction - initialdirection initialdirection = direction turningcounter = 0 stage = 0 t = 0 unit = unitoriginal #with tqdm(total = mex[1], desc = "Progress", leave=False) as pbar: while stage < 2: #==0first unit, so move two units. ==1 propergate step by step. ==2 exit and analyse entire path #take the alt at 10 possitions across this unit #lets get a quick calculated for the magnitude of the direction MAAAAG = np.linalg.norm(direction) # this should = unit if stage == 0: for k in range(subgroupsize): #this starts with 0 point = mex + ((k + 1) * (direction / subgroupsize)) #_,_,miniray[k] = spice.recgeo(point, equatorialradii,flatteningcoefficient) miniray[k] = np.linalg.norm( point) - 3389 #average radii of mars N0 = findrefractivity(miniray, subgroupsize) raystep[:, t] = point #save the last location t = t + 1 stage = stage + 1 if stage == 1: for k in range(subgroupsize): point = raystep[:, t - 1] + ( (k + 1) * (direction / subgroupsize) ) #am i double counting the end of the last and the start of the next? #_,_,miniray[k] = spice.recgeo(point, equatorialradii,flatteningcoefficient) #THIS ONLY WORKS IN 3D # IMPLEMENTING MARS AS A SIMPLE CIRCLE OF AVERAGE 3389 KM RADIUS, !THIS WILL BE UPDATED TO ELLIPSE! miniray[k] = np.linalg.norm(point) - 3389 raystep[:, t] = point #9 is the end of the unit, and refraction always happens relative to the center of refractivity, so rotate off this vector N1 = findrefractivity(miniray, subgroupsize) #IF THE Y VALUE DROPS BELOW 0, LOOP BACK WITH A STEP SIZE OF 1/10TH#################################################################<- HERE if point[1] < 0: #if the position drops below the x axis direction = direction / 10 #drop the step size unit = unit / 10 #stage = stage+1 #MAYBE SHRINK THE DIRECTION INSTEAD OF THE UNIT, IT DOESNT GET REINITIALED IN THIS WHILE LOOP # t is not incrememented so that it can loop back to the last position # , t-1 is the position just before crossing over into negative space if abs(point[1]) < 0.00001: # is it smaller than cm stage = stage + 1 #increase the stage value so the while loop is exited continue # #this section allows for better timing of the function, increment the progresbar by 1 if the current # #position goes one y-value lower # currenty = raystep[1,t] # progress[1] = mex[1] - currenty #this value will be increasing from 0 -> Mex height # increment = np.floor(progress[1])- np.floor(progress[0]) #only when # if increment ==1 : # pbar.update(1) # progress[0] = progress[1] if abs(N1) < 1e-20: #catch for precision errors S[t] = unit - ( N1 * unit ) # whilst neglegible N, Electric distance is can simply be inversly proportional to N t = t + 1 N0 = N1 continue #THE SECTION BELOW IS ONLY ACCESSED WHEN THE REFRACTIVTY IS ABOVE E-20 #print('Current Y possition is', currenty) #this is alt, so is ~3389 km smaller than the vector r = N0 / N1 #NEED MORE PRECISION numorator = N0 + 1 denominator = N1 + 1 rbending = numorator / denominator #average would just add 1 to total N # if t==5000: #only bend when there is a refractive gradient between consecutive air volumes[NO CHANGE IN DIRECTION] # t=t+1 # N0=N1 # continue #this section is only reached if a turning is going to happen # ,testing to see if there is 10 X less turing if the units are 10X smaller -TRUE TEST = float(rbending) if TEST != 1: turningcounter = turningcounter + 1 # !! NOW WITH PRECISION !! #find the angle between the unit (air volume) boarder and the current direction unitrotationaxis = raystep[:, t] / ( (np.linalg.norm(raystep[:, t])) * unit) #unitdirection = direction #MAYBE ALTERING UNITS WILL EFFECT THIS * OR / BY UNIT, CANT FIGURE OUT NOW, OK WHEN UNIT =1 DotProduct = np.dot((unitrotationaxis), direction) AngleofIncidence = (math.pi / 2) - np.arccos( DotProduct) #angle it enters the next air volume #simple snell law to find the bending angle (should be tiny angle) AngleofRefraction = np.arcsin(rbending * np.sin(AngleofIncidence)) # THIS IS NOT EXACTLY WHAT THE TURN IN DIRECTION IS, NEED TO THINK ABOUT rotateby = ((AngleofIncidence - AngleofRefraction) ) #+ve =clockwise, -ve=anticlockwise INCIDENCEDEGREES = np.degrees(AngleofIncidence) REFRACTIONDEGREES = np.degrees(AngleofRefraction) ROTATIONDEGREES = np.degrees(rotateby) #an if statement is required, if this if ROTATIONDEGREES > 1 or ROTATIONDEGREES < -1: print('stophere, u r bending to much') rotationvector = np.array( ((np.cos(rotateby), -np.sin(rotateby)), (np.sin(rotateby), np.cos(rotateby)))) direction = direction.dot(rotationvector) N0 = N1 #store N1 to calc electric distance S[t] = unit - (N1 * unit) t = t + 1 #pbar.refresh() unit_initial = initialdirection / np.linalg.norm(initialdirection) dot_product = np.dot(unit_initial, unitmex) FinalBendingAngle = np.arccos(dot_product) error = np.zeros(t) #print("Number of turns:", turningcounter) #error = swiftmain.finderror(raystep, UnrefractedRay) # also find the y-overshoot here miss = error # 1D along the abscissa #update for the miss angle, going to include miss in the ordinate #START EDITING HERE miss = point[0] - UnrefractedRay[0, -1] # X domain deltaX = UnrefractedRay[0, -1] #TGO X value deltaY = UnrefractedRay[ 1, 0] # this is the height of the whole 2d scene (both refract and unrefacted have the same height) unrefractedangle = np.arctan(deltaX / deltaY) refractedangle = np.arctan((deltaX + miss) / deltaY) missangle = refractedangle - unrefractedangle # if positive, then rotate clockwise #missangle = (np.arcsin(miss/UnrefractedDistance)) # this shouldnt work toc = timer.perf_counter() passingtime = toc - tic #print('miss =', format(miss*1000, '.5f') ,'m || Angle =', np.degrees(missangle) , # '° || Speed =',passingtime,' Sec \n', sep = " ", end= " ", flush =True) if abs(miss) < 1e-4: #is the miss smaller than 10 cm? #ploterrortraces(errorstore,t) break iterationcount = iterationcount + 1 #find the total bending angle at MEX for this final configuration #from the refractive profile from MEX ->TGO, calc the intergral of the change in wavelength to aquire doppler S = S[S != 0] ElectricDistance = ( np.sum(S) ) #N * wavelengths in a km (UNITS MUST BE KEPT THE SAME AS STEP-SIZE)[+ OVERSHOT BECAUSE IT IS A NEGATIVE VARIABLE ] return FinalBendingAngle, ElectricDistance, initialdirection #feedback the starting vector for speed
def producegeometrymeter(MEX, TGO): #maybe completly thin this out, you know this is moslty pointless, what does it actually make class SpiceVariables: obs = '-41' # NAIF code for MEX '-74' target = '-143' # NAIF code for TGO ['EARTH'/'SUN'/ a groundstation etc] 'MARS ODYSSEY' obsfrm = 'IAU_MARS' abcorr = 'NONE' crdsys = 'LATITUDINAL' coord = 'LATITUDE' stepsz = 1.0 # Check every [300] seconds if there is an occultation MAXILV = 100000 #Max number of occultations that can be returned by gfoclt bshape = 'POINT' fshape = 'DSK/UNPRIORITIZED' front = 'MARS' fframe = 'IAU_MARS' TFMT = 'YYYY-MM-DD HR:MN:SC' # Format that Cosmographia understands sv = SpiceVariables() #THIS COULD BE REMOVED # [TGO, _] = spice.spkpos(sv.front, et-when, sv.fframe, 'NONE', sv.target) # [MEX, _] = spice.spkpos(sv.front, et-when, sv.fframe, 'NONE', sv.obs) TGO = TGO + 0 MEX = MEX + 0 #force to be non-strided dist = math.floor(spice.vdist(TGO, MEX)) angleseparation = (spice.vsep(MEX, TGO)) # angle taken a mars center initialangle = (spice.vsep(-MEX, (TGO - MEX))) * ( 180 / math.pi ) # angle taken at mars-MEX-tgo, that points to tgo. needed for the bending functions original starting angle #script needs to work via periods of ray and not meters. [totalperiods is the main iterable, not meters] sc2sc = TGO - MEX norm = np.linalg.norm(sc2sc) unitsc2sc = sc2sc / norm #this needs to shrink if the repeatable expands points = np.empty([3, dist]) sza = np.empty([1, dist]) angleprogression = np.empty([1, dist]) xyzpoints = np.zeros([3, dist]) marsrad = spice.bodvrd(sv.front, 'RADII', 3) flatteningcoefficient = (marsrad[1][0] - marsrad[1][2]) / marsrad[1][0] equatorialradii = marsrad[1][0] # find direction of sun, it will not change much during the occultation. so only calc it once #[SUN, _] = spice.spkpos(sv.front, et, sv.fframe, 'NONE', 'SUN') for i in range(dist): xyzpoint = MEX + ( i * unitsc2sc ) #move along ray, 1000 wavelength distance at a time (685 m). but unitsc2sc is in km... xyzpoints[:, i] = xyzpoint #sza[0,i] = spice.vsep(SUN,xyzpoint) angleprogression[0, i] = (spice.vsep(xyzpoint, MEX)) * (180 / math.pi) points[:, i] = spice.recgeo(xyzpoint, equatorialradii, flatteningcoefficient) points[0, i] = (points[0, i] * (-180 / math.pi)) points[1, i] = (points[1, i] * (-180 / math.pi)) # ray = np.concatenate((points,sza), axis=0) # important for when sza is included #plt.plot(angleprogression[0,:], ray[2,:]) #plt.show() # ray is in lat/lon/alt + sza and xyzpoints is cartesian, both describe the same thing return initialangle, xyzpoints
def 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)
#============================================================================== # Find location of ring points #============================================================================== a_ring_outer_km = 129300 a_ring_inner_km = 122000 x_ring1, y_ring1 = hbt.get_pos_ring(et, name_body='Jupiter', radius=a_ring_inner_km, units='pixels', abcorr='LT', wcs=w) x_ring2, y_ring2 = hbt.get_pos_ring(et, name_body='Jupiter', radius=a_ring_outer_km, units='pixels', abcorr='LT', wcs=w) #============================================================================== # Do the ring extraction #============================================================================== (numrad, rj_array) = sp.bodvrd('JUPITER', 'RADII', 3) # 71492 km rj = rj_array[0] r_ring_inner = 126000 # * rj # Follow same limits as in Throop 2004 J-ring paper fig. 7 r_ring_outer = 132000 # rj num_bins_azimuth = 300 # 500 is OK. 1000 is too many -- we get bins ~0 pixels num_bins_radius = 300 limits_radius = (r_ring_inner, r_ring_outer) # Distances in km # Define an offset in X and Y for the ring. This is the residual navigation error, which we want to apply here. dx = 0 dy = 0 # Do the unwrapping. If there are no ring points, then we catch the error.