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
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
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