Ejemplo n.º 1
0
def compar_orbit(Data_inp_1,
                 Data_inp_2,
                 step_data=900,
                 sats_used_list=['G'],
                 name1='',
                 name2='',
                 use_name_1_2_for_table_name=False,
                 RTNoutput=True,
                 convert_ECEF_ECI=True,
                 clean_null_values=True,
                 conv_coef=10**3,
                 return_satNull=False):
    """
    Compares 2 GNSS orbits files (SP3), and gives a summary plot and a
    statistics table

    Parameters
    ----------
    Data_inp_1 & Data_inp_2 : str or Pandas DataFrame
        contains the orbits or path (string) to the sp3

    step_data : int
        per default data sampling

    sats_used_list : list of str
        used constellation or satellite : G E R C ... E01 , G02 ...
        Individuals satellites are prioritary on whole constellations
        e.g. ['G',"E04"]


    RTNoutput : bool
        select output, Radial Transverse Normal or XYZ

    convert_ECEF_ECI : bool
        convert sp3 ECEF => ECI, must be True in operational !

    name1 & name2 : str (optionals)
        optional custom names for the 2 orbits

    use_name_1_2_for_table_name : bool
        False : use name 1 and 2 for table name, use datafile instead

    clean_null_values : bool or str
        if True or "all" remove sat position in all X,Y,Z values
        are null (0.000000)
        if "any", remove sat position if X or Y or Z is null
        if False, keep everything
        
    conv_coef : int
        conversion coefficient, km to m 10**3, km to mm 10**6

    Returns
    -------
    Diff_sat_all : Pandas DataFrame
    contains differences b/w Data_inp_1 & Data_inp_2
    in Radial Transverse Normal OR XYZ frame

        Attributes of Diff_sat_all :
            Diff_sat_all.name : title of the table

    Note
    ----
    clean_null_values if useful (and necessary) only if
    convert_ECEF_ECI = False
    if convert_ECEF_ECI = True, the cleaning will be done by
    a side effect trick : the convertion ECEF => ECI will generate NaN
    for a zero-valued position
    But, nevertheless, activating  clean_null_values = True is better
    This Note is in fact usefull if you want to see bad positions on a plot
    => Then convert_ECEF_ECI = False and clean_null_values = False

    Source
    ------
    "Coordinate Systems", ASEN 3200 1/24/06 George H. Born

    """

    # selection of both used Constellations AND satellites
    const_used_list = []
    sv_used_list = []
    for sat in sats_used_list:
        if len(sat) == 1:
            const_used_list.append(sat)
        elif len(sat) == 3:
            sv_used_list.append(sat)
            if not sat[0] in const_used_list:
                const_used_list.append(sat[0])

    # Read the files or DataFrames
    # metadata attributes are not copied
    # Thus, manual copy ...
    # (Dirty way, should be impoved without so many lines ...)
    if type(Data_inp_1) is str:
        D1orig = files_rw.read_sp3(Data_inp_1, epoch_as_pd_index=True)
    else:
        D1orig = Data_inp_1.copy(True)
        try:
            D1orig.name = Data_inp_1.name
        except:
            D1orig.name = "no_name"
        try:
            D1orig.path = Data_inp_1.path
        except:
            D1orig.path = "no_path"
        try:
            D1orig.filename = Data_inp_1.filename
        except:
            D1orig.filename = "no_filename"

    if type(Data_inp_2) is str:
        D2orig = files_rw.read_sp3(Data_inp_2, epoch_as_pd_index=True)
    else:
        D2orig = Data_inp_2.copy(True)
        try:
            D2orig.name = Data_inp_2.name
        except:
            D2orig.name = "no_name"
        try:
            D2orig.path = Data_inp_2.path
        except:
            D2orig.path = "no_path"
        try:
            D2orig.filename = Data_inp_2.filename
        except:
            D2orig.filename = "no_filename"

    #### NB : It has been decided with GM that the index of a SP3 dataframe
    ####      will be integers, not epoch datetime anymore
    ####      BUT here, for legacy reasons, the index has to be datetime

    if isinstance(D1orig.index[0], (int, np.integer)):
        D1orig.set_index("epoch", inplace=True)

    if isinstance(D2orig.index[0], (int, np.integer)):
        D2orig.set_index("epoch", inplace=True)

    Diff_sat_stk = []

    # This block is for removing null values
    if clean_null_values:
        if clean_null_values == "all":
            all_or_any = np.all
        elif clean_null_values == "any":
            all_or_any = np.any
        else:
            all_or_any = np.all

        xyz_lst = ['x', 'y', 'z']

        D1_null_bool = all_or_any(np.isclose(D1orig[xyz_lst], 0.), axis=1)
        D2_null_bool = all_or_any(np.isclose(D2orig[xyz_lst], 0.), axis=1)

        D1 = D1orig[np.logical_not(D1_null_bool)]
        D2 = D2orig[np.logical_not(D2_null_bool)]

        if np.any(D1_null_bool) or np.any(D2_null_bool):
            sat_nul = utils.join_improved(
                " ", *list(set(D1orig[D1_null_bool]["sat"])))
            print("WARN : Null values contained in SP3 files : ")
            print(
                "f1:", np.sum(D1_null_bool),
                utils.join_improved(" ",
                                    *list(set(D1orig[D1_null_bool]["sat"]))))
            print(
                "f2:", np.sum(D2_null_bool),
                utils.join_improved(" ",
                                    *list(set(D2orig[D2_null_bool]["sat"]))))
        else:
            sat_nul = []

    else:
        D1 = D1orig.copy()
        D2 = D2orig.copy()

    for constuse in const_used_list:
        D1const = D1[D1['const'] == constuse]
        D2const = D2[D2['const'] == constuse]

        # checking if the data correspond to the step
        bool_step1 = np.mod((D1const.index - np.min(D1.index)).seconds,
                            step_data) == 0
        bool_step2 = np.mod((D2const.index - np.min(D2.index)).seconds,
                            step_data) == 0

        D1window = D1const[bool_step1]
        D2window = D2const[bool_step2]

        # find common sats and common epochs
        sv_set = sorted(
            list(set(D1window['sv']).intersection(set(D2window['sv']))))
        epoc_set = sorted(
            list(set(D1window.index).intersection(set(D2window.index))))

        # if special selection of sats, then apply it
        # (it is late and this selection is incredibely complicated ...)
        if np.any([True if constuse in e else False for e in sv_used_list]):
            # first find the selected sats for the good constellation
            sv_used_select_list = [
                int(e[1:]) for e in sv_used_list if constuse in e
            ]
            #and apply it
            sv_set = sorted(
                list(set(sv_set).intersection(set(sv_used_select_list))))

        for svv in sv_set:
            # First research : find corresponding epoch for the SV
            # this one is sufficent if there is no gaps (e.g. with 0.00000) i.e.
            # same nb of obs in the 2 files
            # NB : .reindex() is smart, it fills the DataFrame
            # with NaN
            try:
                D1sv_orig = D1window[D1window['sv'] == svv].reindex(epoc_set)
                D2sv_orig = D2window[D2window['sv'] == svv].reindex(epoc_set)
            except Exception as exce:
                print("ERR : Unable to re-index with an unique epoch")
                print(
                    "      are you sure there is no multiple-defined epochs for the same sat ?"
                )
                print(
                    "      it happens e.g. when multiple ACs are in the same DataFrame "
                )
                print(
                    "TIP : Filter the input Dataframe before calling this fct with"
                )
                print("      DF = DF[DF['AC'] == 'gbm']")
                raise exce

            # Second research, it is a security in case of gap
            # This step is useless, because .reindex() will fill the DataFrame
            # with NaN
            if len(D1sv_orig) != len(D2sv_orig):
                print("INFO : different epochs nbr for SV", svv,
                      len(D1sv_orig), len(D2sv_orig))
                epoc_sv_set = sorted(
                    list(
                        set(D1sv_orig.index).intersection(set(
                            D2sv_orig.index))))
                D1sv = D1sv_orig.loc[epoc_sv_set]
                D2sv = D2sv_orig.loc[epoc_sv_set]
            else:
                D1sv = D1sv_orig
                D2sv = D2sv_orig

            P1 = D1sv[['x', 'y', 'z']]
            P2 = D2sv[['x', 'y', 'z']]

            # Start ECEF => ECI
            if convert_ECEF_ECI:
                # Backup because the columns xyz will be reaffected
                #D1sv_bkp = D1sv.copy()
                #D2sv_bkp = D2sv.copy()

                P1b = conv.ECEF2ECI(
                    np.array(P1),
                    conv.dt_gpstime2dt_utc(P1.index.to_pydatetime(),
                                           out_array=True))
                P2b = conv.ECEF2ECI(
                    np.array(P2),
                    conv.dt_gpstime2dt_utc(P2.index.to_pydatetime(),
                                           out_array=True))

                D1sv[['x', 'y', 'z']] = P1b
                D2sv[['x', 'y', 'z']] = P2b

                P1 = D1sv[['x', 'y', 'z']]
                P2 = D2sv[['x', 'y', 'z']]
            # End ECEF => ECI

            if not RTNoutput:
                # Compatible with the documentation +
                # empirically tested with OV software
                # it is  P1 - P2 (and not P2 - P1)
                Delta_P = P1 - P2

                Diff_sat = Delta_P.copy()
                Diff_sat.columns = ['dx', 'dy', 'dz']

            else:
                rnorm = np.linalg.norm(P1, axis=1)

                Vx = utils.diff_pandas(D1sv, 'x')
                Vy = utils.diff_pandas(D1sv, 'y')
                Vz = utils.diff_pandas(D1sv, 'z')

                V = pd.concat((Vx, Vy, Vz), axis=1)
                V.columns = ['vx', 'vy', 'vz']

                R = P1.divide(rnorm, axis=0)
                R.columns = ['xnorm', 'ynorm', 'znorm']

                H = pd.DataFrame(np.cross(R, V), columns=['hx', 'hy', 'hz'])
                hnorm = np.linalg.norm(H, axis=1)

                C = H.divide(hnorm, axis=0)
                C.columns = ['hxnorm', 'hynorm', 'hznorm']

                I = pd.DataFrame(np.cross(C, R), columns=['ix', 'iy', 'iz'])

                R_ar = np.array(R)
                I_ar = np.array(I)
                C_ar = np.array(C)

                #R_ar[1]
                Beta = np.stack((R_ar, I_ar, C_ar), axis=1)

                # Compatible with the documentation +
                # empirically tested with OV software
                # it is  P1 - P2 (and not P2 - P1)
                Delta_P = P1 - P2

                # Final determination
                Astk = []

                for i in range(len(Delta_P)):
                    A = np.dot(Beta[i, :, :], np.array(Delta_P)[i])
                    Astk.append(A)

                Diff_sat = pd.DataFrame(np.vstack(Astk),
                                        index=P1.index,
                                        columns=['dr', 'dt', 'dn'])

            Diff_sat = Diff_sat * conv_coef  # metrer conversion

            Diff_sat['const'] = [constuse] * len(Diff_sat.index)
            Diff_sat['sv'] = [svv] * len(Diff_sat.index)
            Diff_sat['sat'] = [constuse + str(svv).zfill(2)] * len(
                Diff_sat.index)

            Diff_sat_stk.append(Diff_sat)

    Diff_sat_all = pd.concat(Diff_sat_stk)
    Date = Diff_sat.index[0]

    # Attribute definition
    if RTNoutput:
        Diff_sat_all.frame_type = 'RTN'

        # Pandas donesn't manage well iterable as attribute
        # So, it is separated
        Diff_sat_all.frame_col_name1 = 'dr'
        Diff_sat_all.frame_col_name2 = 'dt'
        Diff_sat_all.frame_col_name3 = 'dn'

    else:
        # Pandas donesn't manage well iterable as attribute
        # So, it is separated
        Diff_sat_all.frame_col_name1 = 'dx'
        Diff_sat_all.frame_col_name2 = 'dy'
        Diff_sat_all.frame_col_name3 = 'dz'

        if convert_ECEF_ECI:
            Diff_sat_all.frame_type = 'ECI'
        else:
            Diff_sat_all.frame_type = 'ECEF'

    # Name definitions
    if name1:
        Diff_sat_all.name1 = name1
    else:
        Diff_sat_all.name1 = D1orig.name

    if name2:
        Diff_sat_all.name2 = name2
    else:
        Diff_sat_all.name2 = D2orig.name

    Diff_sat_all.filename1 = D1orig.filename
    Diff_sat_all.filename2 = D2orig.filename

    Diff_sat_all.path1 = D1orig.path
    Diff_sat_all.path2 = D2orig.path

    Diff_sat_all.name = ' '.join(
        ('Orbits comparison (' + Diff_sat_all.frame_type + ') b/w',
         Diff_sat_all.name1, '(ref.) and', Diff_sat_all.name2, ',',
         Date.strftime("%Y-%m-%d"), ', doy', str(conv.dt2doy(Date))))

    if return_satNull:
        return Diff_sat_all, sat_nul
    else:
        return Diff_sat_all
Ejemplo n.º 2
0
def extrapolate_sp3_DataFrame(DFsp3,
                              step=900,
                              n_step=9,
                              backward=True,
                              forward=True,
                              until_backward=None,
                              until_forward=None,
                              return_all=True):
    """
    Extrapolate the positions in a SP3 based on the first/last
    epochs' position

    Parameters
    ----------
    DFsp3 : DataFrame
        Input Orbit DataFrame (i.e. generated by files_rw.read_sp3).
    step : int, optional
        step between two epochs. The default is 900.
    n_step : int, optional
        number of epochs to interpolate. The default is 9.
    backward and forward : bool, optional
        extrapolate for the day before/after. The default is True.
    return_all : bool, optional
        if True, returns the input DF enhanced with the extrapolated values
        if False, returns only the extrapolated values.
        The default is True.
    until_backward & until_backward : datetime, optional
        epoch until then the extrapolation has to be done.
        Override n_step
        
    Returns
    -------
    DForb_out : DataFrame
        Orbit DataFrame with extrapolated values
        (see return_all).

    """
    NewEpoch_stk = []

    for sat in DFsp3["sat"].unique():
        print("INFO:extrapolate_sp3_DataFrame: extrapolate: ", sat)
        DFsat = DFsp3[(DFsp3["sat"] == sat) & (DFsp3["type"] == "P")].copy()
        DFsat.sort_values("epoch", inplace=True)

        DFline_dummy = DFsat.iloc[0].copy()
        DFline_dummy["clk"] = 999999.999999

        XYZ = DFsat[["x", "y", "z"]].values * 1000
        Tgps = conv.numpy_dt2dt(DFsat["epoch"].values)
        Tutc = conv.dt_gpstime2dt_utc(Tgps)
        Tsec = [e.total_seconds() for e in (Tutc - Tutc[0])]

        P = conv.ECEF2ECI(XYZ, Tutc)
        V = np.gradient(P, Tsec, axis=0)

        def extrapo_intern_fct(i_ref, coef, until):
            ##
            ## backward :
            ## i_ref , coef = 0,-1
            ##
            ## forward :
            ## i_ref , coef = -1,1
            ##
            ## CORRDS ARE GIVEN IN ECI HERE !!!
            ##
            orbit_back = TwoBodyOrbit("orbit",
                                      mu=3.9860044188e14)  # create an instance
            orbit_back.setOrbCart(Tsec[i_ref], P[i_ref],
                                  V[i_ref])  # define the orbit

            if until:
                t_rang_strt, t_rang_end = list(sorted([Tutc[i_ref], until]))
                Range = conv.dt_range(t_rang_strt, t_rang_end, 0, step)
                n_step_intern = len(Range) - 1
            else:
                n_step_intern = n_step

            for t in np.arange(step, n_step_intern * step + 1, step):
                Pout, Vout = orbit_back.posvelatt(coef * t)
                epoc = Tgps[i_ref] + coef * dt.timedelta(seconds=int(t))
                DFline_new = DFline_dummy.copy()
                DFline_new[["x", "y", "z"]] = Pout
                DFline_new["epoch"] = pd.Timestamp(epoc)

                NewEpoch_stk.append(DFline_new)

            return Pout, Vout

        ### backward
        if backward:
            extrapo_intern_fct(0, -1, until_backward)

        ### forward
        if forward:
            extrapo_intern_fct(-1, 1, until_forward)

    DFNewEpoch = pd.DataFrame(NewEpoch_stk)

    Tutc_NewEpoch = conv.dt_gpstime2dt_utc(
        conv.numpy_dt2dt(DFNewEpoch["epoch"].values))
    DFNewEpoch[["x", "y",
                "z"]] = conv.ECI2ECEF(DFNewEpoch[["x", "y", "z"]].values,
                                      Tutc_NewEpoch)
    DFNewEpoch[["x", "y", "z"]] = DFNewEpoch[["x", "y", "z"]] * 10**-3

    if return_all:
        DForb_out = pd.concat((DFsp3, DFNewEpoch))
    else:
        DForb_out = DFNewEpoch

    DForb_out.reset_index(drop=True, inplace=True)
    DForb_out.sort_values(["sat", "epoch"], inplace=True)
    DForb_out.reset_index(drop=True, inplace=True)

    return DForb_out
Ejemplo n.º 3
0
def extrapolate_sp3_DataFrame(DFsp3, step=900, n_step=9):
    NewEpoch_stk = []

    for sat in DFsp3["sat"].unique():
        print(sat)
        DFsat = DFsp3[(DFsp3["sat"] == sat) & (DFsp3["type"] == "P")]

        DFline_dummy = DFsat.iloc[0].copy()
        DFline_dummy["clk"] = 999999.999999

        XYZ = DFsat[["x", "y", "z"]].values * 1000
        Tgps = conv.datetime64_numpy2dt(DFsat["epoch"].values)
        Tutc = conv.dt_gpstime2dt_utc(Tgps)
        Tsec = [e.total_seconds() for e in (Tutc - Tutc[0])]

        P = conv.ECEF2ECI(XYZ, Tutc)
        V = np.gradient(P, Tsec, axis=0)

        def extrapo_intern_fct(i_ref, coef):
            ##
            ## backward :
            ## i_ref , coef = 0,-1
            ##
            ## forward :
            ## i_ref , coef = -1,1
            ##
            ## CORRDS ARE GIVEN IN ECI HERE !!!
            ##
            orbit_back = TwoBodyOrbit("orbit",
                                      mu=3.9860044188e14)  # create an instance
            orbit_back.setOrbCart(Tsec[i_ref], P[i_ref],
                                  V[i_ref])  # define the orbit
            for t in np.arange(step, n_step * step + 1, step):
                Pout, Vout = orbit_back.posvelatt(coef * t)
                epoc = Tgps[i_ref] + coef * dt.timedelta(seconds=int(t))
                DFline_new = DFline_dummy.copy()
                DFline_new[["x", "y", "z"]] = Pout
                DFline_new["epoch"] = pd.Timestamp(epoc)

                NewEpoch_stk.append(DFline_new)

            return Pout, Vout

        ### backward
        extrapo_intern_fct(0, -1)

        ### forward
        extrapo_intern_fct(-1, 1)

    DFNewEpoch = pd.DataFrame(NewEpoch_stk)

    Tutc_NewEpoch = conv.dt_gpstime2dt_utc(
        conv.datetime64_numpy2dt(DFNewEpoch["epoch"].values))
    DFNewEpoch[["x", "y",
                "z"]] = conv.ECI2ECEF(DFNewEpoch[["x", "y", "z"]].values,
                                      Tutc_NewEpoch)
    DFNewEpoch[["x", "y", "z"]] = DFNewEpoch[["x", "y", "z"]] * .001

    DFout = pd.concat((DFsp3, DFNewEpoch))
    DFout.reset_index(drop=True, inplace=True)
    DFout.sort_values(["sat", "epoch"], inplace=True)
    DFout.reset_index(drop=True, inplace=True)

    return DFout
Ejemplo n.º 4
0
def OrbDF_crf2trf(DForb_inp,
                  DF_EOP_inp,
                  time_scale_inp="gps",
                  inv_trf2crf=False):
    """
    Convert an Orbit DataFrame from Celetrial Reference Frame to 
    Terrestrial Reference Frame (.
    
    Requires EOP to work. Cf. note below.

    Parameters
    ----------
    DForb_inp : DataFrame
        Input Orbit DataFrame in Celetrial Reference Frame.
    DF_EOP_inp : DataFrame
        EOP DataFrame  (C04 format).
    time_scale_inp : str, optional
        The time scale used in. manage 'utc', 'tai' and 'gps'.
        The default is "gps".
    inv_trf2crf : bool, optional
        Provide the inverse transformation TRF => CRF.
        The default is False.

    Returns
    -------
    DForb_out : DataFrame
        Output Orbit DataFrame in Terrestrial Reference Frame.
        (or Celestrial if inv_trf2crf is True)
        
    Note
    ----
    The EOP can be obtained from the IERS C04 products.
    e.g.
    https://datacenter.iers.org/data/latestVersion/224_EOP_C04_14.62-NOW.IAU2000A224.txt
    To get them as a Compatible DataFrame, use the function
    files_rw.read_eop_C04()
    """

    DForb = DForb_inp.copy()

    ### bring everything to UTC
    if time_scale_inp.lower() == "gps":
        DForb["epoch_utc"] = conv.dt_gpstime2dt_utc(DForb["epoch"])
    elif time_scale_inp.lower() == "tai":
        DForb["epoch_utc"] = conv.dt_tai2dt_utc(DForb["epoch"])
    elif time_scale_inp.lower() == "utc":
        DForb["epoch_utc"] = DForb["epoch"]
    ### TT and UT1 are not implemented (quite unlikely to have them as input)

    ### do the time scale's conversion
    DForb["epoch_tai"] = conv.dt_utc2dt_tai(DForb["epoch_utc"])
    DForb["epoch_tt"] = conv.dt_tai2dt_tt(DForb["epoch_tai"])
    DForb["epoch_ut1"] = conv.dt_utc2dt_ut1_smart(DForb["epoch_utc"],
                                                  DF_EOP_inp)

    ### Do the EOP interpolation
    DF_EOP_intrp = eop_interpotate(DF_EOP_inp, DForb["epoch_utc"])
    ### bring the EOP to radians
    Xeop = np.deg2rad(conv.arcsec2deg(DF_EOP_intrp['x']))
    Yeop = np.deg2rad(conv.arcsec2deg(DF_EOP_intrp['y']))

    TRFstk = []

    for tt, ut1, xeop, yeop, x, y, z in zip(DForb["epoch_tt"],
                                            DForb["epoch_ut1"], Xeop, Yeop,
                                            DForb['x'], DForb['y'],
                                            DForb['z']):

        MatCRF22TRF = sofa.iau_c2t06a(2400000.5, conv.dt2MJD(tt), 2400000.5,
                                      conv.dt2MJD(ut1), xeop, yeop)
        if inv_trf2crf:
            MatCRF22TRF = np.linalg.inv(MatCRF22TRF)

        CRF = np.array([x, y, z])
        TRF = np.dot(MatCRF22TRF, CRF)

        TRFstk.append(TRF)

    ### Final stack and replacement
    TRFall = np.vstack(TRFstk)
    DForb_out = DForb_inp.copy()
    DForb_out[["x", "y", "z"]] = TRFall

    return DForb_out