def continue_condition(state_i): # Define the rough ground level lat = ECEF2LLH(np.vstack((state_i[:3])))[0] R_sealevel = EarthRadius(lat) r_end = R_sealevel #+ h_ground r_beg = R_sealevel + 120e3 # brightness = fn() # if brightness < threshold: # Not luminous anymore if norm(state_i[3:6]) < 2e3: # Get a better measure print('Successfully found the end of the trajectory: v < 2km/s.') return False # Ends the integration elif norm(state_i[:3]) > r_beg: # Escaped the atmosphere print('Successfully found the start of the trajectory: h > 120km.') return False # Ends the integration elif norm(state_i[:3]) < r_end: # Reached ground print('Your meteoroid became a meteorite!!') return False # Ends the integration elif state_i[6] < 1e-3: # Lost all mass [<1g] print('Your meteoroid is dust!') return False # Ends the integration else: return True # Continues integration
def PointTriangulation(data, directory=None, mc=1): # Find the times with multiple data points [dt_vals, dt_counts] = np.unique(data['time'], return_counts=True) dt_vals_plus = dt_vals[dt_counts > 1] # Triangulate all paired points x = np.zeros((3, len(dt_vals_plus))) cov = np.zeros((3, 3, len(dt_vals_plus))) for k, dt_val in enumerate(dt_vals_plus): # Get all the observation data obs_ECEF_all = [] for i in range(dt_counts[dt_vals == dt_val][0]): Times = np.array(data['time']) obs_lat = data['obs_lat'][Times == dt_val][i] obs_lon = data['obs_lon'][Times == dt_val][i] obs_hei = data['obs_hei'][Times == dt_val][i] obs_LLH = np.vstack((obs_lat, obs_lon, obs_hei)) obs_ECEF = LLH2ECEF(obs_LLH) obs_ECEF_all.append(obs_ECEF) x_particles = np.zeros((3, mc)) for particle in range(mc): # Calculate each line of sight in the paired points UV_ECEF_all = [] for i in range(dt_counts[dt_vals == dt_val][0]): Times = np.array(data['time']) # Extract some raw data alt = np.deg2rad(data['altitude'][Times == dt_val][i]) azi = np.deg2rad(data['azimuth'][Times == dt_val][i]) if mc != 1: alt_err = np.deg2rad( data['err_plus_altitude'][Times == dt_val][i]) azi_err = np.deg2rad( data['err_plus_azimuth'][Times == dt_val][i]) alt += np.random.normal(0, alt_err) azi += np.random.normal(0, azi_err) # Convert from spherical to cartesian UV_ENU = np.vstack( (np.cos(alt) * np.sin(azi), np.cos(alt) * np.cos(azi), np.sin(alt))) # Convert from ENU to ECEF coordinates UV_ECEF = ENU2ECEF( data['obs_lon'][Times == dt_val][i], data['obs_lat'][Times == dt_val][i]).dot(UV_ENU) UV_ECEF_all.append(UV_ECEF) # Calculate the best position between the first two lines x_est = ShortestMidPoint(obs_ECEF_all, UV_ECEF_all) # Minimizing angular separation between all the lines result = minimize(TotalAngSep, x_est, args=(obs_ECEF_all, UV_ECEF_all), method='Nelder-Mead') x_particles[:, particle] = result.x if mc > 1: PointsTable = Table() PointsTable['datetime'] = [data['datetime'][Times == dt_val][0] ] * mc PointsTable['X_geo'] = x_particles[0] PointsTable['Y_geo'] = x_particles[1] PointsTable['Z_geo'] = x_particles[2] partices_LLH = ECEF2LLH(x_particles) PointsTable['latitude'] = np.rad2deg(partices_LLH[0]) PointsTable['longitude'] = np.rad2deg(partices_LLH[1]) PointsTable['height'] = partices_LLH[2] PointsFile = os.path.join(directory, '{:.3f}_particles.ecsv'.format(dt_val)) PointsTable.write(PointsFile, format='ascii.ecsv', delimiter=',') print('\Points file written to: ' + PointsFile) Points(PointsFile) x[:, k] = np.mean(x_particles, axis=1) cov[:, :, k] = np.cov(x_particles) print('\nTime step: {0:.2f} sec'.format(dt_val)) # print('Normalised Residuals Before: {0:8.4f}'.format( # np.rad2deg(TotalAngSep(x_est, obs_ECEF_all, UV_ECEF_all))*3600)) # print('Normalised Residuals After: {0:8.4f}'.format( # np.rad2deg(result.fun)*3600)) if mc == 1: return x, dt_vals_plus, dt_counts[dt_counts > 1] else: return x, dt_vals_plus, dt_counts[dt_counts > 1], cov
24 * 60 * 60), 2) if errors: mc = 1000 # Number of particles ParticleFolder = os.path.join(PointDir, str(mc) + '_particles_per_timestep') os.mkdir(ParticleFolder) [Pos_ECEF, t_rel, N_cams, Cov_ECEF] = PointTriangulation(AllData, ParticleFolder, mc) else: [Pos_ECEF, t_rel, N_cams] = PointTriangulation(AllData) # Coordinate transforms T_jd = t0.jd + t_rel / (24 * 60 * 60) Pos_LLH = ECEF2LLH(Pos_ECEF) Pos_ECI = ECEF2ECI(Pos_ECEF, Pos_ECEF, T_jd)[0] # Raw velocity calculations vel_eci = norm(Pos_ECI[:, 1:] - Pos_ECI[:, :-1], axis=0) / (t_rel[1:] - t_rel[:-1]) vel_geo = norm(Pos_ECEF[:, 1:] - Pos_ECEF[:, :-1], axis=0) / (t_rel[1:] - t_rel[:-1]) gamma = np.arcsin((Pos_LLH[2][1:] - Pos_LLH[2][:-1]) / (vel_geo * (t_rel[1:] - t_rel[:-1]))) vel_eci = np.hstack((vel_eci, np.nan)) vel_geo = np.hstack((vel_geo, np.nan)) gamma = np.hstack((gamma, np.nan)) t_isot_col = Column(name='datetime', data=Time(T_jd, format='jd', scale='utc').isot)
def NRLMSISE_00(pos, time, pos_type='eci'): ''' Courtesy of Ellie Sansom ''' """ Inputs: inertial position and time Outputs: [altitude, temp, atm_pres, atm density, sos, dyn_vis] """ from nrlmsise_00_header import nrlmsise_input, nrlmsise_output, nrlmsise_flags from nrlmsise_00 import gtd7 time = Time(time, format='jd', scale='utc') # Convert ECI to LLH coordinates if pos_type == 'eci': Pos_LLH = ECEF2LLH(ECI2ECEF_pos(pos, time)) elif pos_type == 'ecef': Pos_LLH = ECEF2LLH(pos) elif pos_type == 'llh': Pos_LLH = pos else: print('NRLMSISE_00 error: Invalid pos_type') exit() g_lat = np.rad2deg(Pos_LLH[0][0]) g_long = np.rad2deg(Pos_LLH[1][0]) alt = Pos_LLH[2][0] # Break up time into year, day of year, and seconds of the day yDay = time.yday.split(':') yr = float(yDay[0]) doy = float(yDay[1]) sec = float(yDay[2]) * 60 * 60 + float(yDay[3]) * 60 + float(yDay[4]) # Assign our variables into the nrmsise inputs Input = nrlmsise_input(yr, doy, sec, alt / 1000, g_lat, g_long) Output = nrlmsise_output() Flags = nrlmsise_flags() # Switches for i in range(1, 24): Flags.switches[i] = 1 # GTD7 atmospheric model subroutine gtd7(Input, Flags, Output) # Temperature at alt [deg K] T = Output.t[1] # Molecular number densities [m-3] He = Output.d[0] # He O = Output.d[1] # O N2 = Output.d[2] # N2 O2 = Output.d[3] # O2 Ar = Output.d[4] # Ar H = Output.d[6] # H N = Output.d[7] # N # ano_O = Output.d[8] # Anomalous oxygen sum_mass = He + O + N2 + O2 + Ar + H + N # Molar mass He_mass = 4.0026 # g/mol O_mass = 15.9994 # g/mol N2_mass = 28.013 # g/mol O2_mass = 31.998 # g/mol Ar_mass = 39.948 # g/mol H_mass = 1.0079 # g/mol N_mass = 14.0067 # g/mol # Molecular weight of air [kg/mol] mol_mass_air = (He_mass * He + O_mass * O + N2_mass * N2 + O2_mass * O2 + Ar_mass * Ar + H_mass * H + N_mass * N) / (1000 * sum_mass) # Total mass density [kg*m-3] po = Output.d[5] * 1000 Ru = 8.3144621 # Universal gas constant [J/(K*mol)] R = Ru / mol_mass_air # Individual gas constant [J/(kg*K)] #287.058 # Ideal gas law atm_pres = po * T * R # Speed of sound in atm sos = 331.3 * np.sqrt(1 + T / 273.15) # Dynamic viscosity (http://en.wikipedia.org/wiki/Viscosity) C = 120 #Sutherland's constant for air [deg K] mu_ref = 18.27e-6 # Reference viscosity [[mu_Pa s] * e-6] T_ref = 291.15 # Reference temperature [deg K] dyn_vis = mu_ref * (T_ref + C) / (T + C) * (T / T_ref)**1.5 return T, atm_pres, po, sos, dyn_vis
def EarthDynamics(t, X, WindData, t0, return_abs_mag=False): ''' The state rate dynamics are used in Runge-Kutta integration method to calculate the next set of equinoctial element values. ''' ''' State Rates ''' # State parameter vector decomposed Pos_ECI = np.vstack((X[:3])) Vel_ECI = np.vstack((X[3:6])) M = X[6] rho = X[7] A = X[8] c_ml = X[9] # c_massloss = sigma*cd_hyp t_jd = t0 + t / (24 * 60 * 60) # Absolute time [jd] ''' Primary Gravitational Acceleration ''' a_grav = gravity_vector(Pos_ECI) ''' Atmospheric Drag Perturbation - Better Model Needed ''' Pos_ECEF = ECI2ECEF_pos(Pos_ECI, t_jd) Pos_LLH = ECEF2LLH(Pos_ECEF) # Atmospheric velocity if type(WindData) == Table: #1D vertical profile maxwindheight = max(WindData['# Height']) if float(Pos_LLH[2]) > maxwindheight: [v_atm, rho_a, temp] = AtmosphericModel([], Pos_ECI, t_jd) else: [v_atm, rho_a, temp] = AtmosphericModel(WindData, Pos_ECI, t_jd) else: #3D wind profile maxwindheight = max(WindData[2, :, :, :]) if float(Pos_LLH[2]) > maxwindheight: [v_atm, rho_a, temp] = AtmosphericModel([], Pos_ECI, t_jd) else: [Wind_ENU, rho_a, temp] = WRF3D(WindData, Pos_LLH) Wind_ECEF = ENU2ECEF(Pos_LLH[1], Pos_LLH[0]).dot(Wind_ENU) v_atm = ECEF2ECI(Pos_ECEF, Wind_ECEF, t_jd)[1] # Velocity relative to the atmosphere v_rel = Vel_ECI - v_atm v = norm(v_rel) # New drag equations - function that fits the literature cd = dragcoeff(v, temp, rho_a, A)[0] # Total drag perturbation a_drag = -cd * A * rho_a * v * v_rel / (2 * M**(1. / 3) * rho**(2. / 3)) ''' Total Perturbing Acceleration ''' a_tot = a_grav + a_drag # Mass-loss equation dm_dt = -c_ml * A * rho_a * v**3 * M**(2. / 3) / (2 * rho**(2. / 3)) # See (Sansom, 2019) as reference if return_abs_mag: # sigma = c_ml / cd lum = -X[10] * (v**2 / 2 + cd / c_ml) * dm_dt * 1e7 return -2.5 * np.log10(lum / 1.5e10) ''' State Rate Equation ''' X_dot = np.zeros(X.shape) X_dot[:3] = Vel_ECI.flatten() X_dot[3:6] = a_tot.flatten() X_dot[6] = dm_dt return X_dot
def Propagate(state0, args): ''' Inputs: Initial ECI position (m), ECI velocity (m/s), and mass (kgs). Outputs: ECI position (m), ECI velocity (m/s) throughout the dark flight. ''' # Calculate the meteor's initial state parameters Pos_ECI = np.vstack((state0[1:4])) t0 = float(state0[0]) X = state0.flatten()[1:] # Add mass to the state [WindData, h_ground, windspd_err, winddir_err, mc] = args newWindData = copy.deepcopy(WindData) if mc: # Randomise every layer independently if type(newWindData) == Table: #wind profile # newWindData['Wind'] = WindData['Wind'] + np.random.normal( 0.0, windspd_err, size = len(WindData['Wind']) ) # newWindData['WDir'] = WindData['WDir'] + np.random.normal( 0.0, winddir_err, size = len(WindData['Wind']) ) newWindData['Wind'] = WindData['Wind'] + np.random.uniform( -windspd_err, windspd_err, size=len(WindData['Wind'])) newWindData['WDir'] = WindData['WDir'] + np.random.uniform( -windspd_err, winddir_err, size=len(WindData['Wind'])) newWindData['WDir'] = np.mod(newWindData['WDir'], 360.0) #mod the wind to 0-360a else: #3d wind ###TODO untested #winddata[3] wind north newWindData[3, :, :, :] = WindData[3, :, :, :] + np.random.uniform( -windspd_err, windspd_err, size=np.shape(WindData[3, :, :, :])) #winddata[4] wind east newWindData[4, :, :, :] = WindData[4, :, :, :] + np.random.uniform( -windspd_err, windspd_err, size=np.shape(WindData[4, :, :, :])) #winddata[5] wind up #left untouched elevation_data = srtm.get_data(local_cache_dir=SRTM_cache) # Initialise the time step # R_sealevel = EarthRadius(ECEF2LLH(Pos_ECI)[0]) # r_end = R_sealevel + h_ground state, T_rel = [], [] def solout(t, X): state.extend([X.copy()]) T_rel.extend([t]) Pos_ECI = np.vstack((X[:3])) Pos_ECF = ECI2ECEF_pos(Pos_ECI, t0 + (t / (24 * 60 * 60))) lt, ln, ht = ECEF2LLH(Pos_ECF) R_sealevel = EarthRadius(lt) if h_ground == 'a': # print( 'lat,lon, time, ', np.rad2deg(lt).item(), np.rad2deg(ln).item(), str(t+t0) ) h_gnd = elevation_data.get_elevation( np.rad2deg(lt).item(), np.rad2deg(ln).item()) if h_gnd == None: #in case of voids or geo file not found print('WARNING elevation file issue, ', np.rad2deg(lt).item(), np.rad2deg(ln).item(), ht, X[6]) h_gnd = 0.0 else: h_gnd = float(h_ground) r_end = R_sealevel + h_gnd if norm(X[:3]) < r_end: # Reached ground or below return -1 # Ends the integration elif X[6] < 1e-3: # Lost all mass [<1g] print('Your meteoroid is dust!') return -1 # Ends the integration else: return 0 # Continues integration # Setup integrator dt0 = 0.1 dt_max = 3 #60 # sec solver = ode(EarthDynamics).set_integrator('dopri5', \ first_step=dt0, max_step=dt_max, rtol=1e-4) #'dop853', solver.set_solout(solout) solver.set_initial_value(X, 0).set_f_params(newWindData, t0) # Integrate with RK4 until impact t_max = np.inf solver.integrate(t_max) # Assign the variables T = np.array(T_rel) / (24 * 60 * 60) + t0 X_all = np.array(state).T # Make sure we end precisely on the ground, rather than below #so we backtrack a fraction of a step Pos_ECF_final = ECI2ECEF_pos(X_all[:3, -1], T[-1]) lt, ln, ht = ECEF2LLH(Pos_ECF_final) if h_ground == 'a': h_gnd = elevation_data.get_elevation( np.rad2deg(lt).item(), np.rad2deg(ln).item()) if h_gnd == None: h_gnd = 0.0 print('WARNING final ground issue, ', np.rad2deg(lt).item(), np.rad2deg(ln).item(), ht, X_all[6]) else: h_gnd = float(h_ground) r_end = EarthRadius(lt) + h_gnd fraction = (norm(X_all[:3, -2:-1]) - r_end) / (norm(X_all[:3, -2:-1]) - norm(X_all[:3, -1:])) if len(X_all[0]) > 1: X_all[:, -1:] = X_all[:, -2:-1] + fraction * (X_all[:, -1:] - X_all[:, -2:-1]) T[-1] = T[-2] + fraction * (T[-1] - T[-2]) Pos_ECI = X_all[:3] Vel_ECI = X_all[3:6] M = X_all[6] rho = X_all[7] A = X_all[8] c_ml = X_all[9] return { 'time_jd': T, 'pos_eci': Pos_ECI, 'vel_eci': Vel_ECI, 'mass': M, 'rho': rho, 'shape': A, 'c_ml': c_ml }
def AtmosphericModel(WindData, Pos_ECI, t_jd): # The rotational component of the wind in the ECI frame north_pole_ecef = np.vstack((0, 0, 6356.75231e3)) north_pole_eci = ECEF2ECI_pos(north_pole_ecef, t_jd) earth_ang_rot = OMEGA_EARTH * north_pole_eci / norm(north_pole_eci) v_rot = np.cross(earth_ang_rot, Pos_ECI, axis=0) if len(WindData): # Pos_LLH = ECEF2LLH(Pos_ECI) # There is a max of +-40m height error here Pos_ECEF = ECEF2LLH(Pos_ECI) Pos_LLH = ECEF2LLH(Pos_ECEF) h = float(Pos_LLH[2]) # (m) # Get the relevent wind data from that height if h > max( WindData['# Height']): # Think about using a model for +30km i = np.argmax(WindData['# Height']) Wind = WindData['Wind'][i] # (m/s) WDir = WindData['WDir'][i] # (deg) TempK = WindData['TempK'][i] # (deg K) Press = WindData['Press'][i] # (Pa) RHum = WindData['RHum'][i] # (%) elif h <= max(WindData['# Height']) and h >= min(WindData['# Height']): Wind = interp1d(WindData['# Height'], WindData['Wind'], kind='cubic')(h) WDir = interp1d(WindData['# Height'], WindData['WDir'], kind='cubic')(h) TempK = interp1d(WindData['# Height'], WindData['TempK'], kind='cubic')(h) Press = interp1d(WindData['# Height'], WindData['Press'], kind='cubic')(h) RHum = interp1d(WindData['# Height'], WindData['RHum'], kind='cubic')(h) elif h < min(WindData['# Height']): i = np.argmin(WindData['# Height']) Wind = WindData['Wind'][i] # (m/s) WDir = WindData['WDir'][i] # (deg) TempK = WindData['TempK'][i] # (deg K) Press = WindData['Press'][i] # (Pa) RHum = WindData['RHum'][i] # (%) else: input('There is a problem in AtomosphericModel.') # Calculate the atmospheric density rho_a = density_from_pressure(TempK, Press, RHum) # (kg/m3) # Construct the wind vector (start with clockwise N=0, coming from!!) Wind_ENU = -np.vstack((Wind * np.sin(np.deg2rad(WDir)), Wind * np.cos(np.deg2rad(WDir)), 0)) # Convert the ENU to ECI coordinates (using lat/lon from ECI frame) lat = float(Pos_LLH[0]) lon = float(Pos_LLH[1]) # (rad) C_ENU2ECI = np.array([[ -np.sin(lon), -np.sin(lat) * np.cos(lon), np.cos(lat) * np.cos(lon) ], [ np.cos(lon), -np.sin(lat) * np.sin(lon), np.cos(lat) * np.sin(lon) ], [0, np.cos(lat), np.sin(lat)]]) Wind_ECI = np.dot(C_ENU2ECI, Wind_ENU) + v_rot else: #no winds, use NRLMSISE model Pos_ECEF = ECI2ECEF_pos(Pos_ECI, t_jd) Pos_LLH = ECEF2LLH(Pos_ECEF) h = float(Pos_LLH[2]) # (m) [TempK, Press, rho_a] = NRLMSISE_00(Pos_LLH, t_jd, pos_type='llh')[:3] Wind_ENU = np.vstack((0, 0, 0)) Wind_ECI = v_rot # Save the variables WRF_history.append(np.vstack((h, Wind_ENU, rho_a, TempK))) return Wind_ECI, rho_a, TempK
def Propagate(state0, args): ''' Inputs: Initial ECI position (m), ECI velocity (m/s), and mass (kgs). Outputs: ECI position (m), ECI velocity (m/s) throughout the dark flight. ''' # Calculate the meteor's initial state parameters Pos_ECI = np.vstack((state0[1:4])) t0 = float(state0[0]) X = state0.flatten()[1:] # Add mass to the state [WindData, h_ground] = args # Initialise the time step R_sealevel = EarthRadius(ECEF2LLH(Pos_ECI)[0]) r_end = R_sealevel + h_ground state, T_rel = [], [] def solout(t, X): state.extend([X.copy()]) T_rel.extend([t]) if norm(X[:3]) < r_end: # Reached ground return -1 # Ends the integration elif X[6] < 1e-3: # Lost all mass [<1g] print('Your meteoroid is dust!') return -1 # Ends the integration else: return 0 # Continues integration # Setup integrator dt0 = 0.1 dt_max = 5 #60 # sec solver = ode(EarthDynamics).set_integrator('dopri5', \ first_step=dt0, max_step=dt_max, rtol=1e-4) #'dop853', solver.set_solout(solout) solver.set_initial_value(X, 0).set_f_params(WindData, t0) # Integrate with RK4 until impact t_max = np.inf solver.integrate(t_max) # Assign the variables T = np.array(T_rel) / (24 * 60 * 60) + t0 X_all = np.array(state).T # Make sure we end on the ground r_end = EarthRadius(ECEF2LLH(X_all[:3, -1:])[0]) + h_ground fraction = (norm(X_all[:3, -2:-1]) - r_end) / (norm(X_all[:3, -2:-1]) - norm(X_all[:3, -1:])) if len(X_all[0]) > 1: X_all[:, -1:] = X_all[:, -2:-1] + fraction * (X_all[:, -1:] - X_all[:, -2:-1]) T[-1] = T[-2] + fraction * (T[-1] - T[-2]) Pos_ECI = X_all[:3] Vel_ECI = X_all[3:6] M = X_all[6] rho = X_all[7] A = X_all[8] c_ml = X_all[9] return { 'time_jd': T, 'pos_eci': Pos_ECI, 'vel_eci': Vel_ECI, 'mass': M, 'rho': rho, 'shape': A, 'c_ml': c_ml }
def EarthDynamics(t, X, WindData, t0, return_abs_mag=False): ''' The state rate dynamics are used in Runge-Kutta integration method to calculate the next set of equinoctial element values. ''' ''' State Rates ''' # State parameter vector decomposed Pos_ECI = np.vstack((X[:3])) Vel_ECI = np.vstack((X[3:6])) M = X[6] rho = X[7] A = X[8] c_ml = X[9] # c_massloss = sigma*cd_hyp t_jd = t0 + t / (24 * 60 * 60) # Absolute time [jd] ''' Primary Gravitational Acceleration ''' a_grav = gravity_vector(Pos_ECI) ''' Atmospheric Drag Perturbation - Better Model Needed ''' # Atmospheric velocity if type(WindData) == Table: #1D vertical profile [v_atm, rho_a, temp] = AtmosphericModel(WindData, Pos_ECI, t_jd) else: #3D wind profile Pos_ECEF = ECI2ECEF_pos(Pos_ECI, t_jd) Pos_LLH = ECEF2LLH(Pos_ECEF) [Wind_ENU, rho_a, temp] = WRF3D(WindData, Pos_LLH) Wind_ECEF = ENU2ECEF(Pos_LLH[1], Pos_LLH[0]).dot(Wind_ENU) v_atm = ECEF2ECI(Pos_ECEF, Wind_ECEF, t_jd)[1] # Velocity relative to the atmosphere v_rel = Vel_ECI - v_atm v = norm(v_rel) # # Constants for drag coeff # d = 2 * np.sqrt(A / rho**(2./3) * M**(2./3)) # mu_a = viscosity(temp) # Air Viscosity (Pa.s) # mach = v / SoS(temp) # Mach Number # re = reynolds(v, rho_a, mu_a, d) # Reynolds Number # kn = knudsen(mach, re) # Knudsen Number # cd = dragcoef(re, mach, kn)#, A) # Drag Coefficient # # cd = 2.0 # Approximation # New drag equations - function that fits the literature cd = dragcoeff(v, temp, rho_a, A)[0] # cd = 1 # # New drag equations 2 # mach = v / SoS(temp) # Mach Number # cd = dragcoefff(mach, A) # Total drag perturbation a_drag = -cd * A * rho_a * v * v_rel / (2 * M**(1. / 3) * rho**(2. / 3)) ''' Total Perturbing Acceleration ''' a_tot = a_grav + a_drag # Mass-loss equation dm_dt = -c_ml * A * rho_a * v**3 * M**(2. / 3) / (2 * rho**(2. / 3)) # See (Sansom, 2019) as reference if return_abs_mag: # sigma = c_ml / cd lum = -X[10] * (v**2 / 2 + cd / c_ml) * dm_dt * 1e7 return -2.5 * np.log10(lum / 1.5e10) ''' State Rate Equation ''' X_dot = np.zeros(X.shape) X_dot[:3] = Vel_ECI.flatten() X_dot[3:6] = a_tot.flatten() X_dot[6] = dm_dt return X_dot
def write_rays_kml(obs_lat, obs_lon, obs_hei, alt, azi, dist, cam_name, outputname, colour='33ff0000'): # Convert from spherical to cartesian UV_ENU = np.vstack( (np.cos(alt) * np.sin(azi), np.cos(alt) * np.cos(azi), np.sin(alt))) UV_ECEF = ENU2ECEF(obs_lon, obs_lat).dot(UV_ENU) Obs_ECEF = LLH2ECEF(np.vstack((obs_lat, obs_lon, obs_hei))) Proj_ECEF = Obs_ECEF + dist * UV_ECEF Proj_LLH = ECEF2LLH(Proj_ECEF) [Proj_lat, Proj_lon, Proj_hei] = [Proj_LLH[0], Proj_LLH[1], Proj_LLH[2]] # Open the file to be written. f = open(outputname, 'w') f.write('<?xml version="1.0" encoding="UTF-8"?>\n') f.write('<kml xmlns="http://earth.google.com/kml/2.1">\n') f.write('<Document>\n') f.write('<open>1</open>\n') f.write('<Placemark>\n') f.write(' <Style id="camera">\n') f.write(' <LineStyle>\n') f.write(' <width>1</width>\n') f.write(' </LineStyle>\n') f.write(' <PolyStyle>\n') f.write(' <color>' + str(colour) + '</color>\n') f.write(' </PolyStyle>\n') f.write(' </Style>\n') f.write(' <styleUrl>#camera</styleUrl>\n') f.write('<name>' + str(cam_name) + '</name>\n') f.write('<Polygon>\n') f.write(' <extrude>0</extrude>\n') f.write(' <altitudeMode>absolute</altitudeMode>\n') f.write(' <outerBoundaryIs>\n') f.write(' <LinearRing>\n') f.write(' <coordinates>\n') f.write(' ' + str(np.rad2deg(obs_lon)) + ',' + str(np.rad2deg(obs_lat)) + ',' + str(obs_hei) + '\n') for i in range(len(Proj_lat)): f.write(' ' + str(np.rad2deg(Proj_lon[i])) + "," + str(np.rad2deg(Proj_lat[i])) + "," + str(Proj_hei[i]) + '\n') f.write(' ' + str(np.rad2deg(obs_lon)) + ',' + str(np.rad2deg(obs_lat)) + ',' + str(obs_hei) + '\n') f.write(' </coordinates>\n') f.write(' </LinearRing>\n') f.write(' </outerBoundaryIs>\n') f.write('</Polygon>\n') f.write('</Placemark>\n') f.write('</Document>\n') f.write('</kml>\n') f.close() return {'fname': outputname, 'kml_type': 'rays', 'camera': cam_name}