def _radiantDiff(radiant_eq, ra_g, dec_g, v_init, state_vector, jd_ref): ra_a, dec_a = radiant_eq # Convert the given RA and Dec to ECI coordinates radiant_eci = np.array(raDec2ECI(ra_a, dec_a)) # Estimate the orbit with the given apparent radiant orbit = calcOrbit(radiant_eci, v_init, v_init, state_vector, jd_ref, stations_fixed=False, \ reference_init=True) if orbit.ra_g is None: return None # Compare the difference between the calculated and the reference geocentric radiant return angleBetweenSphericalCoords(orbit.dec_g, orbit.ra_g, dec_g, ra_g)
def loadFTPDetectInfo(ftpdetectinfo_file_name, stations, time_offsets=None, join_broken_meteors=True): """ Arguments: ftpdetectinfo_file_name: [str] Path to the FTPdetectinfo file. stations: [dict] A dictionary where the keys are stations IDs, and values are lists of: - latitude +N in radians - longitude +E in radians - height in meters Keyword arguments: time_offsets: [dict] (key, value) pairs of (stations_id, time_offset) for every station. None by default. join_broken_meteors: [bool] Join meteors broken across 2 FF files. Return: meteor_list: [list] A list of MeteorObservation objects filled with data from the FTPdetectinfo file. """ meteor_list = [] with open(ftpdetectinfo_file_name) as f: # Skip the header for i in range(11): next(f) current_meteor = None bin_name = False cal_name = False meteor_header = False for line in f: line = line.replace('\n', '').replace('\r', '') # Skip the line if it is empty if not line: continue if '-----' in line: # Mark that the next line is the bin name bin_name = True # If the separator is read in, save the current meteor if current_meteor is not None: current_meteor.finish() meteor_list.append(current_meteor) continue if bin_name: bin_name = False # Mark that the next line is the calibration file name cal_name = True # Save the name of the FF file ff_name = line # Extract the reference time from the FF bin file name line = line.split('_') # Count the number of string segments, and determine if it the old or new CAMS format if len(line) == 6: sc = 1 else: sc = 0 ff_date = line[1 + sc] ff_time = line[2 + sc] milliseconds = line[3 + sc] year = ff_date[:4] month = ff_date[4:6] day = ff_date[6:8] hour = ff_time[:2] minute = ff_time[2:4] seconds = ff_time[4:6] year, month, day, hour, minute, seconds, milliseconds = map(int, [year, month, day, hour, minute, seconds, milliseconds]) # Calculate the reference JD time jdt_ref = date2JD(year, month, day, hour, minute, seconds, milliseconds) continue if cal_name: cal_name = False # Mark that the next line is the meteor header meteor_header = True continue if meteor_header: meteor_header = False line = line.split() # Get the station ID and the FPS from the meteor header station_id = line[0].strip() fps = float(line[3]) # Try converting station ID to integer try: station_id = int(station_id) except: pass # If the time offsets were given, apply the correction to the JD if time_offsets is not None: if station_id in time_offsets: print('Applying time offset for station {:s} of {:.2f} s'.format(str(station_id), \ time_offsets[station_id])) jdt_ref += time_offsets[station_id]/86400.0 else: print('Time offset for given station not found!') # Get the station data if station_id in stations: lat, lon, height = stations[station_id] else: print('ERROR! No info for station ', station_id, ' found in CameraSites.txt file!') print('Exiting...') break # Init a new meteor observation current_meteor = MeteorObservation(jdt_ref, station_id, lat, lon, height, fps, \ ff_name=ff_name) continue # Read in the meteor observation point if (current_meteor is not None) and (not bin_name) and (not cal_name) and (not meteor_header): line = line.replace('\n', '').split() # Read in the meteor frame, RA and Dec frame_n = float(line[0]) x = float(line[1]) y = float(line[2]) ra = float(line[3]) dec = float(line[4]) azim = float(line[5]) elev = float(line[6]) # Read the visual magnitude, if present if len(line) > 8: mag = line[8] if mag == 'inf': mag = None else: mag = float(mag) else: mag = None # Add the measurement point to the current meteor current_meteor.addPoint(frame_n, x, y, azim, elev, ra, dec, mag) # Add the last meteor the the meteor list if current_meteor is not None: current_meteor.finish() meteor_list.append(current_meteor) ### Concatenate observations across different FF files ### if join_broken_meteors: # Go through all meteors and compare the next observation merged_indices = [] for i in range(len(meteor_list)): # If the next observation was merged, skip it if (i + 1) in merged_indices: continue # Get the current meteor observation met1 = meteor_list[i] if i >= (len(meteor_list) - 1): break # Get the next meteor observation met2 = meteor_list[i + 1] # Compare only same station observations if met1.station_id != met2.station_id: continue # Extract frame number met1_frame_no = int(met1.ff_name.split("_")[-1].split('.')[0]) met2_frame_no = int(met2.ff_name.split("_")[-1].split('.')[0]) # Skip if the next FF is not exactly 256 frames later if met2_frame_no != (met1_frame_no + 256): continue # Check for frame continouty if (met1.frames[-1] < 254) or (met2.frames[0] > 2): continue ### Check if the next frame is close to the predicted position ### # Compute angular distance between the last 2 points on the first FF ang_dist = angleBetweenSphericalCoords(met1.dec_data[-2], met1.ra_data[-2], met1.dec_data[-1], \ met1.ra_data[-1]) # Compute frame difference between the last frame on the 1st FF and the first frame on the 2nd FF df = met2.frames[0] + (256 - met1.frames[-1]) # Skip the pair if the angular distance between the last and first frames is 2x larger than the # frame difference times the expected separation ang_dist_between = angleBetweenSphericalCoords(met1.dec_data[-1], met1.ra_data[-1], \ met2.dec_data[0], met2.ra_data[0]) if ang_dist_between > 2*df*ang_dist: continue ### ### ### If all checks have passed, merge observations ### # Recompute the frames frames = 256.0 + met2.frames # Recompute the time data time_data = frames/met1.fps # Add the observations to first meteor object met1.frames = np.append(met1.frames, frames) met1.time_data = np.append(met1.time_data, time_data) met1.x_data = np.append(met1.x_data, met2.x_data) met1.y_data = np.append(met1.y_data, met2.y_data) met1.azim_data = np.append(met1.azim_data, met2.azim_data) met1.elev_data = np.append(met1.elev_data, met2.elev_data) met1.ra_data = np.append(met1.ra_data, met2.ra_data) met1.dec_data = np.append(met1.dec_data, met2.dec_data) met1.mag_data = np.append(met1.mag_data, met2.mag_data) # Sort all observations by time met1.finish() # Indicate that the next observation is to be skipped merged_indices.append(i + 1) ### ### # Removed merged meteors from the list meteor_list = [element for i, element in enumerate(meteor_list) if i not in merged_indices] return meteor_list
def calcTrajSimDiffs(traj_sim_pairs, radiant_extent, vg_extent): """ Given the pairs of trajectories and simulations, compute radiant and velocity differences. Arguments: traj_sim_pairs: [list] A list of (Trajectory, SimMeteor) pairs. radiant_extent: [float] Maximum radiant error (deg). If the error is larger, the trajectory will be counted as a failure. vg_extent: [float] Maxium velocity error (km/s). If the error is larger, the trajectory will be counted as a failure. Return: radiant_diffs, vg_diffs, conv_angles, failed_count: [list of lists] - radiant_diffs - radiant errors (deg) - vg_diffs - velocity errors (km/s) - conv_angles - convergence angles (deg) - failed_count - number of trajectories outside the radiant and velocity error bounds """ vg_diffs = [] radiant_diffs = [] conv_angles = [] failed_count = 0 # Go through all the pairs and calculate the difference in the geocentric velocity and distance between # the true and the estimated radiant for entry in traj_sim_pairs: traj, sim = entry # Skip the orbit if it was not estimated properly if traj.orbit.v_g is None: failed_count += 1 continue # Difference in the geocentric velocity (km/s) vg_diff = (traj.orbit.v_g - sim.v_g) / 1000 # Difference in radiant (degrees) radiant_diff = np.degrees(angleBetweenSphericalCoords(sim.dec_g, sim.ra_g, traj.orbit.dec_g, \ traj.orbit.ra_g)) # Check if the results are within the given extents if (radiant_diff > radiant_extent) or (abs(vg_diff) > vg_extent): failed_count += 1 continue vg_diffs.append(vg_diff) radiant_diffs.append(radiant_diff) # Store the convergence angle if hasattr(traj, 'best_conv_inter'): # This is a wmpl trajectory object conv_ang = traj.best_conv_inter.conv_angle else: # Gural type trajectory object conv_ang = traj.max_convergence conv_angles.append(np.degrees(conv_ang)) return radiant_diffs, vg_diffs, conv_angles, failed_count
def plotRadiants(pickle_trajs, plot_type='geocentric', ra_cent=None, dec_cent=None, radius=1, plt_handle=None, label=None, plot_stddev=True, **kwargs): """ Plots geocentric radiants of the given pickle files. Arguments: pickle_trajs: [list] A list of trajectory objects loaded from .pickle files. Keyword arguments: plot_type: [str] Type of radiants to plot. - 'geocentric' - RA_g, Dec_g, Vg plot - 'heliocentric ecliptic' - Lh, Bg, Vh plot ra_cent: [float] Right ascension used for selecing only radiants in given radius on the sky (degrees). dec_cent: [float] Declination used for selecing only radiants in given radius on the sky (degrees). radius: [float] Radius for selecting radiants centred on ra_cent, dec_cent (degrees). plt_handle: [plt object] Matplotlib plt handle (e.g. plt variable when doing plt.plot(...)). label: [str] Label for the legend, used only when plot_stddev=True plot_stddev: [bool] Add standard deviation in the legend label. True by default. """ ra_list = [] dec_list = [] vg_list = [] sol_list = [] lh_list = [] lh_std_list = [] bh_list = [] bh_std_list = [] vh_list = [] vh_std_list = [] for traj in pickle_trajs: # Don't take trajectories where the radiant is not calculated if traj.orbit.ra_g is None: continue # Check if the coordinates are within the given radius (if such central coordinates are given at all) if ra_cent is not None: # Calculate the angle between the centre RA/Dec and the given point. Skip the point if it is # outside the given radius if angleBetweenSphericalCoords(np.radians(dec_cent), np.radians(ra_cent), traj.orbit.dec_g, \ traj.orbit.ra_g) > np.radians(radius): continue ra_list.append(traj.orbit.ra_g) dec_list.append(traj.orbit.dec_g) vg_list.append(traj.orbit.v_g) sol_list.append(traj.orbit.la_sun) lh_list.append(traj.orbit.L_h) bh_list.append(traj.orbit.B_h) vh_list.append(traj.orbit.v_h) if traj.uncertainties is not None: lh_std_list.append(traj.uncertainties.L_h) bh_std_list.append(traj.uncertainties.B_h) vh_std_list.append(traj.uncertainties.v_h) ra_list = np.array(ra_list) dec_list = np.array(dec_list) vg_list = np.array(vg_list) sol_list = np.array(sol_list) lh_list = np.array(lh_list) lh_std_list = np.array(lh_std_list) bh_list = np.array(bh_list) bh_std_list = np.array(bh_std_list) vh_list = np.array(vh_list) vh_std_list = np.array(vh_std_list) # Choose the appropriate coordinates for plotting if plot_type == 'geocentric': x_list = ra_list y_list = dec_list z_list = vg_list / 1000 # Create inputs for calculating the distance profile distance_input = [] for ra, dec, sol, vg in zip(ra_list, dec_list, sol_list, vg_list): distance_input.append([ra, dec, sol, vg / 1000]) # Calculate the distance profile dist_profile = calculateDistanceProfile(distance_input, calcDN) elif plot_type == 'heliocentric ecliptic': x_list = lh_list y_list = bh_list z_list = vh_list / 1000 # Create inputs for calculating the distance profile distance_input = [] if traj.uncertainties is not None: for Lh, Lh_std, Bh, Bh_std, sol, vh, vh_std in zip( lh_list, lh_std_list, bh_list, bh_std_list, sol_list, vh_list, vh_std_list): distance_input.append( [Lh, Lh_std, Bh, Bh_std, sol, vh / 1000, vh_std / 1000]) # Calculate the distance profile dist_profile = calculateDistanceProfile(distance_input, calcDVuncert) else: for Lh, Bh, sol, vh in zip(lh_list, bh_list, sol_list, vh_list): distance_input.append([Lh, Bh, sol, vh / 1000]) # Calculate the distance profile dist_profile = calculateDistanceProfile(distance_input, calcDV) print(np.c_[np.degrees(x_list), np.degrees(y_list), z_list]) if plt_handle is None: plt_handle = CelestialPlot(x_list, y_list, projection='stere', bgcolor='k') if plot_stddev: if label is None: label = '' ra_stddev = np.degrees(scipy.stats.circstd(x_list)) dec_stddev = np.degrees(np.std(y_list)) label += "{:d} orbits, $\sigma_{{RA}}$ = {:.2f}$\degree$".format( len(x_list), ra_stddev) label += ", " label += "$\sigma_{{Dec}}$ = {:.2f}$\degree$".format(dec_stddev) plt_handle.scatter(x_list, y_list, c=z_list, label=label, **kwargs) return plt_handle, dist_profile
def associateShower(la_sun, L_g, B_g, v_g, sol_window=1.0, max_radius=3.0, \ max_veldif_percent=10.0): """ Given a shower radiant in Sun-centered ecliptic coordinates, associate it to a meteor shower using the showers listed in the Jenniskens et al. (2018) paper. Arguments: la_sun: [float] Solar longitude (radians). L_g: [float] Sun-centered ecliptic longitude (i.e. geocentric ecliptic longitude minus the solar longitude) (radians). B_g: [float] Sun-centered geocentric ecliptic latitude (radians). v_g: [float] Geocentric velocity (m/s). Keyword arguments: sol_window: [float] Solar longitude window of association (deg). max_radius: [float] Maximum angular separation from reference radiant (deg). max_veldif_percent: [float] Maximum velocity difference in percent. Return: [MeteorShower instance] MeteorShower instance for the closest match, or None for sporadics. """ # Create a working copy of the Jenniskens shower list temp_shower_list = copy.deepcopy(jenniskens_shower_list) # Find all showers in the solar longitude window la_sun_diffs = np.abs((temp_shower_list[:, 0] - la_sun + np.pi)%(2*np.pi) - np.pi) temp_shower_list = temp_shower_list[la_sun_diffs <= np.radians(sol_window)] # Check if any associations were found if not len(temp_shower_list): return None # Find all showers within the maximum radiant distance radius radiant_distances = angleBetweenSphericalCoords(temp_shower_list[:, 2], temp_shower_list[:, 1], B_g, \ (L_g - la_sun)%(2*np.pi)) temp_shower_list = temp_shower_list[radiant_distances <= np.radians(max_radius)] # Check if any associations were found if not len(temp_shower_list): return None # Find all showers within the maximum velocity difference limit velocity_diff_percents = np.abs(100*(temp_shower_list[:, 3] - v_g)/temp_shower_list[:, 3]) temp_shower_list = temp_shower_list[velocity_diff_percents <= max_veldif_percent] # Check if any associations were found if not len(temp_shower_list): return None ### Choose the best matching shower by the solar longitude, radiant, and velocity closeness ### # Compute the closeness parameters as a sum of normalized closeness by every individual parameter sol_dist_norm = np.abs(((temp_shower_list[:, 0] - la_sun + np.pi)%(2*np.pi) \ - np.pi))/np.radians(sol_window) rad_dist_norm = angleBetweenSphericalCoords(temp_shower_list[:, 2], temp_shower_list[:, 1], B_g, (L_g \ - la_sun)%(2*np.pi))/np.radians(max_radius) vg_dist_norm = np.abs(100*(temp_shower_list[:, 3] - v_g)/temp_shower_list[:, 3])/max_veldif_percent closeness_param = sol_dist_norm + rad_dist_norm + vg_dist_norm # Choose the best matching shower best_shower = temp_shower_list[np.argmin(closeness_param)] ### ### # Init a shower object l0, L_l0, B_g, v_g, IAU_no = best_shower shower_obj = MeteorShower(l0, (L_l0 + l0)%360, B_g, v_g, int(round(IAU_no))) return shower_obj
traj_sim_pairs = pairTrajAndSim(traj_list, sim_meteors) vinit_stds = [] radiant_stds = [] radiant_errors = [] for (traj, sim) in traj_sim_pairs: # Skip the orbit if it was not estimated properly if traj.orbit.v_g is None: continue # Difference in the initial velocity (m/s) vinit_diff = traj.v_init - sim.v_begin # Difference in geocentric radiant (degrees) radiant_diff = np.degrees(angleBetweenSphericalCoords(sim.dec_g, sim.ra_g, traj.orbit.dec_g, \ traj.orbit.ra_g)) # Reject everything larger than the threshold if radiant_diff > radiant_diff_max: continue # # Skip zero velocity uncertainties # if traj.uncertainties.v_init == 0: # continue # # Compute the number of standard deviations of the real error in the velocity # vinit_diff_std = vinit_diff/traj.uncertainties.v_init # # # Skip all cases where the difference is larger than 0.5 km/s # # if np.abs(vinit_diff_std) > 500: # # continue