Esempio n. 1
0
    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)
Esempio n. 2
0
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