Exemple #1
0
def calcOrbit(radiant_eci, v_init, v_avg, eci_ref, jd_ref, stations_fixed=False, reference_init=True, \
    rotation_correction=False):
    """ Calculate the meteor's orbit from the given meteor trajectory. The orbit of the meteoroid is defined 
        relative to the centre of the Sun (heliocentric).

    Arguments:
        radiant_eci: [3 element ndarray] Radiant vector in ECI coordinates (meters).
        v_init: [float] Initial velocity (m/s).
        v_avg: [float] Average velocity of a meteor (m/s).
        eci_ref: [float] reference ECI coordinates in the epoch of date (meters, in the epoch of date) of the 
            meteor trajectory. They can be calculated with the geo2Cartesian function. Ceplecha (1987) assumes 
            this to the the average point on the trajectory, while Jennsikens et al. (2011) assume this to be 
            the first point on the trajectory as that point is not influenced by deceleration.
            NOTE: If the stations are not fixed, the reference ECI coordinates should be the ones
            of the initial point on the trajectory, NOT of the average point!
        jd_ref: [float] reference Julian date of the meteor trajectory. Ceplecha (1987) takes this as the 
            average time of the trajectory, while Jenniskens et al. (2011) take this as the the first point
            on the trajectory.
    
    Keyword arguments:
        stations_fixed: [bool] If True, the correction for Earth's rotation will be performed on the radiant,
            but not the velocity. This should be True ONLY in two occasions:
                - if the ECEF coordinate system was used for trajectory estimation
                - if the ECI coordinate system was used for trajectory estimation, BUT the stations were not
                    moved in time, but were kept fixed at one point, regardless of the trajectory estimation
                    method.
            It is necessary to perform this correction for the intersecting planes method, but not for
            the lines of sight method ONLY when the stations are not fixed. Of course, if one is using the 
            lines of sight method with fixed stations, one should perform this correction!
        reference_init: [bool] If True (default), the initial point on the trajectory is given as the reference
            one, i.e. the reference ECI coordinates are the ECI coordinates of the initial point on the
            trajectory, where the meteor has the velocity v_init. If False, then the reference point is the
            average point on the trajectory, and the average velocity will be used to do the corrections.
        rotation_correction: [bool] If True, the correction of the initial velocity for Earth's rotation will
            be performed. False by default. This should ONLY be True if the coordiante system for trajectory
            estimation was ECEF, i.e. did not rotate with the Earth. In all other cases it should be False, 
            even if fixed station coordinates were used in the ECI coordinate system!

    Return:
        orb: [Orbit object] Object containing the calculated orbit.

    """

    ### Correct the velocity vector for the Earth's rotation if the stations are fixed ###
    ##########################################################################################################

    eci_x, eci_y, eci_z = eci_ref

    # Calculate the geocentric latitude (latitude which considers the Earth as an elipsoid) of the reference
    # trajectory point
    lat_geocentric = np.arctan2(eci_z, np.sqrt(eci_x**2 + eci_y**2))

    # Calculate the dynamical JD
    jd_dyn = jd2DynamicalTimeJD(jd_ref)

    # Calculate the geographical coordinates of the reference trajectory ECI position
    lat_ref, lon_ref, ht_ref = cartesian2Geo(jd_ref, *eci_ref)

    # Initialize a new orbit structure and assign calculated parameters
    orb = Orbit()

    # Calculate the velocity of the Earth rotation at the position of the reference trajectory point (m/s)
    v_e = 2 * np.pi * vectMag(eci_ref) * np.cos(lat_geocentric) / 86164.09053

    # Calculate the equatorial coordinates of east from the reference position on the trajectory
    azimuth_east = np.pi / 2
    altitude_east = 0
    ra_east, dec_east = altAz2RADec(azimuth_east, altitude_east, jd_ref,
                                    lat_ref, lon_ref)

    # Compute velocity components of the state vector
    if reference_init:

        # If the initial velocity was the reference velocity, use it for the correction
        v_ref_vect = v_init * radiant_eci

    else:
        # Calculate reference velocity vector using the average point on the trajectory and the average
        # velocity
        v_ref_vect = v_avg * radiant_eci

    # Apply the Earth rotation correction if the station coordinates are fixed (a MUST for the
    # intersecting planes method!)
    if stations_fixed:

        ### Set fixed stations radiant info ###

        # If the stations are fixed, then the input state vector is already fixed to the ground
        orb.ra_norot, orb.dec_norot = eci2RaDec(radiant_eci)

        # Apparent azimuth and altitude (no rotation)
        orb.azimuth_apparent_norot, orb.elevation_apparent_norot = raDec2AltAz(orb.ra_norot, orb.dec_norot, \
            jd_ref, lat_ref, lon_ref)

        # Estimated average velocity (no rotation)
        orb.v_avg_norot = v_avg

        # Estimated initial velocity (no rotation)
        orb.v_init_norot = v_init

        ### ###

        v_ref_corr = np.zeros(3)

        # Calculate the corrected reference velocity vector/radiant
        v_ref_corr[0] = v_ref_vect[0] - v_e * np.cos(ra_east)
        v_ref_corr[1] = v_ref_vect[1] - v_e * np.sin(ra_east)
        v_ref_corr[2] = v_ref_vect[2]

    else:

        # MOVING STATIONS
        # Velocity vector will remain unchanged if the stations were moving
        if reference_init:
            v_ref_corr = v_init * radiant_eci

        else:
            v_ref_corr = v_avg * radiant_eci

        ### ###
        # If the rotation correction does not have to be applied, meaning that the rotation is already
        # included, compute a version of the radiant and the velocity without Earth's rotation
        # (REPORTING PURPOSES ONLY, THESE VALUES ARE NOT USED IN THE CALCULATION)

        v_ref_nocorr = np.zeros(3)

        # Calculate the derotated reference velocity vector/radiant
        v_ref_nocorr[0] = v_ref_vect[0] + v_e * np.cos(ra_east)
        v_ref_nocorr[1] = v_ref_vect[1] + v_e * np.sin(ra_east)
        v_ref_nocorr[2] = v_ref_vect[2]

        # Compute the radiant without Earth's rotation included
        orb.ra_norot, orb.dec_norot = eci2RaDec(vectNorm(v_ref_nocorr))
        orb.azimuth_apparent_norot, orb.elevation_apparent_norot = raDec2AltAz(orb.ra_norot, orb.dec_norot, \
            jd_ref, lat_ref, lon_ref)
        orb.v_init_norot = vectMag(v_ref_nocorr)
        orb.v_avg_norot = orb.v_init_norot - v_init + v_avg

        ### ###

    ##########################################################################################################

    ### Correct velocity for Earth's gravity ###
    ##########################################################################################################

    # If the reference velocity is the initial velocity
    if reference_init:

        # Use the corrected velocity for Earth's rotation (when ECEF coordinates are used)
        if rotation_correction:
            v_init_corr = vectMag(v_ref_corr)

        else:
            # IMPORTANT NOTE: The correction in this case is only done on the radiant (even if the stations
            # were fixed, but NOT on the initial velocity!). Thus, correction from Ceplecha 1987,
            # equation (35) is not needed. If the initial velocity is determined from time vs. length and in
            # ECI coordinates, whose coordinates rotate with the Earth, the moving stations play no role in
            # biasing the velocity.
            v_init_corr = v_init

    else:

        if rotation_correction:

            # Calculate the corrected initial velocity if the reference velocity is the average velocity
            v_init_corr = vectMag(v_ref_corr) + v_init - v_avg

        else:
            v_init_corr = v_init

    # Calculate apparent RA and Dec from radiant state vector
    orb.ra, orb.dec = eci2RaDec(radiant_eci)
    orb.v_init = v_init
    orb.v_avg = v_avg

    # Calculate the apparent azimuth and altitude (geodetic latitude, because ra/dec are calculated from ECI,
    #   which is calculated from WGS84 coordinates)
    orb.azimuth_apparent, orb.elevation_apparent = raDec2AltAz(
        orb.ra, orb.dec, jd_ref, lat_ref, lon_ref)

    orb.jd_ref = jd_ref
    orb.lon_ref = lon_ref
    orb.lat_ref = lat_ref
    orb.ht_ref = ht_ref
    orb.lat_geocentric = lat_geocentric

    # Assume that the velocity in infinity is the same as the initial velocity (after rotation correction, if
    # it was needed)
    orb.v_inf = v_init_corr

    # Make sure the velocity of the meteor is larger than the escape velocity
    if v_init_corr**2 > (2 * 6.67408 * 5.9722) * 1e13 / vectMag(eci_ref):

        # Calculate the geocentric velocity (sqrt of squared inital velocity minus the square of the Earth escape
        # velocity at the height of the trajectory), units are m/s.
        # Square of the escape velocity is: 2GM/r, where G is the 2014 CODATA-recommended value of
        # 6.67408e-11 m^3/(kg s^2), and the mass of the Earth is M = 5.9722e24 kg
        v_g = np.sqrt(v_init_corr**2 -
                      (2 * 6.67408 * 5.9722) * 1e13 / vectMag(eci_ref))

        # Calculate the radiant corrected for Earth's rotation (ONLY if the stations were fixed, otherwise it
        #   is the same as the apparent radiant)
        ra_corr, dec_corr = eci2RaDec(vectNorm(v_ref_corr))

        # Calculate the Local Sidreal Time of the reference trajectory position
        lst_ref = np.radians(jd2LST(jd_ref, np.degrees(lon_ref))[0])

        # Calculate the apparent zenith angle
        zc = np.arccos(np.sin(dec_corr)*np.sin(lat_geocentric) \
            + np.cos(dec_corr)*np.cos(lat_geocentric)*np.cos(lst_ref - ra_corr))

        # Calculate the zenith attraction correction
        delta_zc = 2 * np.arctan2(
            (v_init_corr - v_g) * np.tan(zc / 2), v_init_corr + v_g)

        # Zenith distance of the geocentric radiant
        zg = zc + np.abs(delta_zc)

        ##########################################################################################################

        ### Calculate the geocentric radiant ###
        ##########################################################################################################

        # Get the azimuth from the corrected RA and Dec
        azimuth_corr, _ = raDec2AltAz(ra_corr, dec_corr, jd_ref,
                                      lat_geocentric, lon_ref)

        # Calculate the geocentric radiant
        ra_g, dec_g = altAz2RADec(azimuth_corr, np.pi / 2 - zg, jd_ref,
                                  lat_geocentric, lon_ref)

        ### Precess ECI coordinates to J2000 ###

        # Convert rectangular to spherical coordiantes
        re, delta_e, alpha_e = cartesianToSpherical(*eci_ref)

        # Precess coordinates to J2000
        alpha_ej, delta_ej = equatorialCoordPrecession(jd_ref, J2000_JD.days,
                                                       alpha_e, delta_e)

        # Convert coordinates back to rectangular
        eci_ref = sphericalToCartesian(re, delta_ej, alpha_ej)
        eci_ref = np.array(eci_ref)

        ######

        # Precess the geocentric radiant to J2000
        ra_g, dec_g = equatorialCoordPrecession(jd_ref, J2000_JD.days, ra_g,
                                                dec_g)

        # Calculate the ecliptic latitude and longitude of the geocentric radiant (J2000 epoch)
        L_g, B_g = raDec2Ecliptic(J2000_JD.days, ra_g, dec_g)

        # Load the JPL ephemerids data
        jpl_ephem_data = SPK.open(config.jpl_ephem_file)

        # Get the position of the Earth (km) and its velocity (km/s) at the given Julian date (J2000 epoch)
        # The position is given in the ecliptic coordinates, origin of the coordinate system is in the centre
        # of the Sun
        earth_pos, earth_vel = calcEarthRectangularCoordJPL(
            jd_dyn, jpl_ephem_data, sun_centre_origin=True)

        # print('Earth position:')
        # print(earth_pos)
        # print('Earth velocity:')
        # print(earth_vel)

        # Convert the Earth's position to rectangular equatorial coordinates (FK5)
        earth_pos_eq = rotateVector(earth_pos, np.array([1, 0, 0]),
                                    J2000_OBLIQUITY)

        # print('Earth position (FK5):')
        # print(earth_pos_eq)

        # print('Meteor ECI:')
        # print(eci_ref)

        # Add the position of the meteor's trajectory to the position of the Earth to calculate the
        # equatorial coordinates of the meteor (in kilometers)
        meteor_pos = earth_pos_eq + eci_ref / 1000

        # print('Meteor position (FK5):')
        # print(meteor_pos)

        # Convert the position of the trajectory from FK5 to heliocentric ecliptic coordinates
        meteor_pos = rotateVector(meteor_pos, np.array([1, 0, 0]),
                                  -J2000_OBLIQUITY)

        # print('Meteor position:')
        # print(meteor_pos)

        ##########################################################################################################

        # Calculate components of the heliocentric velocity of the meteor (km/s)
        v_h = np.array(earth_vel) + np.array(
            eclipticToRectangularVelocityVect(L_g, B_g, v_g / 1000))

        # Calculate the heliocentric velocity in km/s
        v_h_mag = vectMag(v_h)

        # Calculate the corrected heliocentric ecliptic coordinates of the meteoroid using the method of
        # Sato and Watanabe (2014).
        L_h, B_h, met_v_h = correctedEclipticCoord(L_g, B_g, v_g / 1000,
                                                   earth_vel)

        # Calculate the solar longitude
        la_sun = jd2SolLonJPL(jd_dyn)

        # Calculations below done using Dave Clark's Master thesis equations

        # Specific orbital energy
        epsilon = (vectMag(v_h)**2) / 2 - SUN_MU / vectMag(meteor_pos)

        # Semi-major axis in AU
        a = -SUN_MU / (2 * epsilon * AU)

        # Calculate mean motion in rad/day
        n = np.sqrt(G * SUN_MASS / ((np.abs(a) * AU * 1000.0)**3)) * 86400.0

        # Calculate the orbital period in years
        T = 2 * np.pi * np.sqrt(
            ((a * AU)**3) / SUN_MU) / (86400 * SIDEREAL_YEAR)

        # Calculate the orbit angular momentum
        h_vect = np.cross(meteor_pos, v_h)

        # Calculate inclination
        incl = np.arccos(h_vect[2] / vectMag(h_vect))

        # Calculate eccentricity
        e_vect = np.cross(v_h, h_vect) / SUN_MU - vectNorm(meteor_pos)
        eccentricity = vectMag(e_vect)

        # Calculate perihelion distance (source: Jenniskens et al., 2011, CAMS overview paper)
        if eccentricity == 1:
            q = (vectMag(meteor_pos) +
                 np.dot(e_vect, meteor_pos)) / (1 + vectMag(e_vect))
        else:
            q = a * (1.0 - eccentricity)

        # Calculate the aphelion distance
        Q = a * (1.0 + eccentricity)

        # Normal vector to the XY reference frame
        k_vect = np.array([0, 0, 1])

        # Vector from the Sun pointing to the ascending node
        n_vect = np.cross(k_vect, h_vect)

        # Calculate node
        if vectMag(n_vect) == 0:
            node = 0
        else:
            node = np.arctan2(n_vect[1], n_vect[0])

        node = node % (2 * np.pi)

        # Calculate argument of perihelion
        if vectMag(n_vect) != 0:
            peri = np.arccos(
                np.dot(n_vect, e_vect) / (vectMag(n_vect) * vectMag(e_vect)))

            if e_vect[2] < 0:
                peri = 2 * np.pi - peri

        else:
            peri = np.arccos(e_vect[0] / vectMag(e_vect))

        peri = peri % (2 * np.pi)

        # Calculate the longitude of perihelion
        pi = (node + peri) % (2 * np.pi)

        ### Calculate true anomaly
        true_anomaly = np.arccos(
            np.dot(e_vect, meteor_pos) /
            (vectMag(e_vect) * vectMag(meteor_pos)))
        if np.dot(meteor_pos, v_h) < 0:
            true_anomaly = 2 * np.pi - true_anomaly

        true_anomaly = true_anomaly % (2 * np.pi)

        ###

        # Calculate eccentric anomaly
        eccentric_anomaly = np.arctan2(np.sqrt(1 - eccentricity**2)*np.sin(true_anomaly), eccentricity \
            + np.cos(true_anomaly))

        # Calculate mean anomaly
        mean_anomaly = eccentric_anomaly - eccentricity * np.sin(
            eccentric_anomaly)
        mean_anomaly = mean_anomaly % (2 * np.pi)

        # Calculate the time in days since the last perihelion passage of the meteoroid
        dt_perihelion = (mean_anomaly * a**(3.0 / 2)) / 0.01720209895

        if not np.isnan(dt_perihelion):

            # Calculate the date and time of the last perihelion passage
            last_perihelion = jd2Date(jd_dyn - dt_perihelion, dt_obj=True)

        else:
            last_perihelion = None

        # Calculate Tisserand's parameter with respect to Jupiter
        Tj = 2 * np.sqrt(
            (1 - eccentricity**2) * a / 5.204267) * np.cos(incl) + 5.204267 / a

        # Assign calculated parameters
        orb.lst_ref = lst_ref
        orb.jd_dyn = jd_dyn
        orb.v_g = v_g
        orb.ra_g = ra_g
        orb.dec_g = dec_g

        orb.meteor_pos = meteor_pos
        orb.L_g = L_g
        orb.B_g = B_g

        orb.v_h_x, orb.v_h_y, orb.v_h_z = met_v_h
        orb.L_h = L_h
        orb.B_h = B_h

        orb.zc = zc
        orb.zg = zg

        orb.v_h = v_h_mag * 1000

        orb.la_sun = la_sun

        orb.a = a
        orb.e = eccentricity
        orb.i = incl
        orb.peri = peri
        orb.node = node
        orb.pi = pi
        orb.q = q
        orb.Q = Q
        orb.true_anomaly = true_anomaly
        orb.eccentric_anomaly = eccentric_anomaly
        orb.mean_anomaly = mean_anomaly
        orb.last_perihelion = last_perihelion
        orb.n = n
        orb.T = T

        orb.Tj = Tj

    return orb
Exemple #2
0
def getAtmDensity(lat, lon, height, jd):
    """ For the given heights, returns the atmospheric density from NRLMSISE-00 model. 
    
    More info: https://github.com/magnific0/nrlmsise-00/blob/master/nrlmsise-00.h

    Arguments:
        lat: [float] Latitude in radians.
        lon: [float] Longitude in radians.
        height: [float] Height in meters.
        jd: [float] Julian date.

    Return:
        [float] Atmosphere density in kg/m^3.

    """

    # Init the input array
    inp = nrlmsise_input()

    # Convert the given Julian date to datetime
    dt = jd2Date(jd, dt_obj=True)

    # Get the day of year
    doy = dt.timetuple().tm_yday

    # Get the second in day
    midnight = dt.replace(hour=0, minute=0, second=0, microsecond=0)
    sec = (dt - midnight).seconds

    # Calculate the Local sidreal time (degrees)
    lst, _ = jd2LST(jd, np.degrees(lon))

    ### INPUT PARAMETERS ###
    ##########################################################################################################
    # Set year (no effect)
    inp.year = 0

    # Day of year
    inp.doy = doy

    # Seconds in a day
    inp.sec = sec

    # Altitude in kilometers
    inp.alt = height / 1000.0

    # Geodetic latitude (deg)
    inp.g_lat = np.degrees(lat)

    # Geodetic longitude (deg)
    inp.g_long = np.degrees(lon)

    # Local apparent solar time (hours)
    inp.lst = lst / 15

    # f107, f107A, and ap effects are neither large nor well established below 80 km and these parameters
    # should be set to 150., 150., and 4. respectively.

    # 81 day average of 10.7 cm radio flux (centered on DOY)
    inp.f107A = 150

    # Daily 10.7 cm radio flux for previous day
    inp.f107 = 150

    # Magnetic index (daily)
    inp.ap = 4

    ##########################################################################################################

    # Init the flags array
    flags = nrlmsise_flags()

    # Set output in kilograms and meters
    flags.switches[0] = 1

    # Set all switches to ON
    for i in range(1, 24):
        flags.switches[i] = 1

    # Array containing the following magnetic values:
    #   0 : daily AP
    #   1 : 3 hr AP index for current time
    #   2 : 3 hr AP index for 3 hrs before current time
    #   3 : 3 hr AP index for 6 hrs before current time
    #   4 : 3 hr AP index for 9 hrs before current time
    #   5 : Average of eight 3 hr AP indicies from 12 to 33 hrs prior to current time
    #   6 : Average of eight 3 hr AP indicies from 36 to 57 hrs prior to current time
    aph = ap_array()

    # Set all AP indices to 100
    for i in range(7):
        aph.a[i] = 100

    # Init the output array
    # OUTPUT VARIABLES:
    #     d[0] - HE NUMBER DENSITY(CM-3)
    #     d[1] - O NUMBER DENSITY(CM-3)
    #     d[2] - N2 NUMBER DENSITY(CM-3)
    #     d[3] - O2 NUMBER DENSITY(CM-3)
    #     d[4] - AR NUMBER DENSITY(CM-3)
    #     d[5] - TOTAL MASS DENSITY(GM/CM3) [includes d[8] in td7d]
    #     d[6] - H NUMBER DENSITY(CM-3)
    #     d[7] - N NUMBER DENSITY(CM-3)
    #     d[8] - Anomalous oxygen NUMBER DENSITY(CM-3)
    #     t[0] - EXOSPHERIC TEMPERATURE
    #     t[1] - TEMPERATURE AT ALT
    out = nrlmsise_output()

    # Evaluate the atmosphere with the given parameters
    gtd7(inp, flags, out)

    # Get the total mass density
    atm_density = out.d[5]

    return atm_density
Exemple #3
0
def writeMiligInputFile(jdt_ref,
                        meteor_list,
                        file_path,
                        convergation_fact=1.0):
    """ Write the MILIG input file. 

    Arguments:
        jdt_ref: [float] reference Julian date.
        meteor_list: [list] A list of StationData objects
        file_path: [str] Path to the MILIG input file which will be written.

    Keyword arguments:
        convergation_fact: [float] Convergation control factor. Iteration is stopped when increments of all 
            parameters are smaller than a fixed value. This factor scales those fixed values such that 
            tolerance can be increased or decreased. By default, evcorr sets this to 0.01 (stricter 
            tolerances) and METAL uses 1.0 (default tolerances).

    Return:
        None
    """

    # Take the first station's longitude for the GST calculation
    lon = meteor_list[0].lon

    # Calculate the Greenwich Mean Time
    _, gst = jd2LST(jdt_ref, np.degrees(lon))

    datetime_obj = jd2Date(jdt_ref, dt_obj=True)

    with open(file_path, 'w') as f:

        datetime_str = datetime_obj.strftime("%Y%m%d%H%M%S.%f")[:16]

        # Write the first line with the date, GST and Convergation control factor
        f.write(datetime_str +
                '{:10.3f}{:10.3f}\n'.format(gst, convergation_fact))

        # Go through every meteor
        for meteor in meteor_list:

            # Write station ID and meteor coordinates. The weight of the station is set to 1
            f.write("{:3d}{:+10.5f}{:10.6f}{:5.3f}{:5.2f}\n".format(
                int(meteor.station_id), np.degrees(meteor.lon),
                np.degrees(meteor.lat), meteor.height / 1000.0, 1.0))

            # Go through every point in the meteor
            for i, (azim, zangle, t) in enumerate(zip(meteor.azim_data, meteor.zangle_data, \
                meteor.time_data)):

                last_pick = 0

                # If this is the last point, last_pick is 9
                if i == len(meteor.time_data) - 1:
                    last_pick = 9

                # Write individual meteor points. If the 4th column is 1, the point will be ignored.
                if t < 0:
                    time_format = "{:+8.5f}"
                else:
                    time_format = "{:8.6f}"
                f.write(("{:9.5f}{:8.5}{:3d}{:3d}" + time_format + "\n").format(np.degrees(azim), \
                    np.degrees(zangle), last_pick, 0, t))

        # Flag indicating that the meteor data ends here
        f.write('-1\n')

        # Initial aproximations
        f.write(' 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n')

        # Optional parameters
        f.write('RFIX\n \n')