def plot_obstype_availability( self, figure_name: str = "plot_obstype_availability_{system}.{FIGURE_FORMAT}", ) -> List[pathlib.PosixPath]: """Generate GNSS observation type observation type availability based on RINEX observation file Args: figure_name: File name of figure. Returns: List with figure path for observation type availability depending on GNSS. File name ends with GNSS identifier (e.g. 'E', 'G'), for example 'plot_obstype_availability_E.png'. """ figure_paths = list() for sys in sorted(self.dset.unique("system")): x_arrays = [] y_arrays = [] idx_sys = self.dset.filter(system=sys) num_sat = len(set(self.dset.satellite[idx_sys])) figure_path = self.figure_dir / figure_name.replace( "{system}", sys).replace("{FIGURE_FORMAT}", FIGURE_FORMAT) figure_paths.append(figure_path) for sat in sorted(self.dset.unique("satellite"), reverse=True): if not sat.startswith(sys): continue idx_sat = self.dset.filter(satellite=sat) keep_idx = np.full(self.dset.num_obs, False, dtype=bool) for obstype in self._sort_string_array( self.dset.meta["obstypes"][sys]): keep_idx[idx_sat] = np.logical_not( np.isnan(self.dset.obs[obstype][idx_sat])) #time_diff = np.diff(self.dset.time.gps.gps_seconds[keep_idx]) #time_diff = np.insert(time_diff, 0, float('nan')) if np.any(keep_idx): num_obs = len(self.dset.time[keep_idx]) x_arrays.append(self.dset.time.gps.datetime[keep_idx]) y_arrays.append(np.full(num_obs, f"{sat}_{obstype}")) plot( x_arrays=x_arrays, y_arrays=y_arrays, xlabel="Time [GPS]", ylabel="Satellite and observation type", figure_path=figure_path, y_unit="", opt_args={ "colormap": "tab20", "figsize": (1.0 * num_sat, 3.0 * num_sat), "fontsize": 5, "plot_to": "file", "plot_type": "scatter", #"title": "Satellite and observation type", }, ) return figure_paths
def plot_skyplot( self, figure_name: str = "plot_skyplot_{system}.{FIGURE_FORMAT}", ) -> List[pathlib.PosixPath]: """Plot skyplot for each GNSS Args: figure_name: File name of figure. Returns: List with figure path for skyplot depending on GNSS. File name ends with GNSS identifier (e.g. 'E', 'G'), for example 'plot_skyplot_E.png'. """ figure_paths = list() # Convert azimuth to range 0-360 degree azimuth = self.dset.site_pos.azimuth idx = azimuth < 0 azimuth[idx] = 2 * np.pi + azimuth[idx] # Convert zenith distance from radian to degree zenith_distance = np.rad2deg(self.dset.site_pos.zenith_distance) # Generate x- and y-axis data per system for sys in sorted(self.dset.unique("system")): x_arrays = [] y_arrays = [] labels = [] figure_path = self.figure_dir / figure_name.replace( "{system}", sys).replace("{FIGURE_FORMAT}", FIGURE_FORMAT) figure_paths.append(figure_path) for sat in sorted(self.dset.unique("satellite")): if not sat.startswith(sys): continue idx = self.dset.filter(satellite=sat) x_arrays.append(azimuth[idx]) y_arrays.append(zenith_distance[idx]) labels.append(sat) # Plot with polar projection # TODO: y-axis labels are overwritten after second array plot. Why? What to do? plot( x_arrays=x_arrays, y_arrays=y_arrays, xlabel="", ylabel="", y_unit="", labels=labels, figure_path=figure_path, opt_args={ "colormap": "hsv", "figsize": (7, 7.5), "legend": True, "legend_ncol": 6, "legend_location": "bottom", "plot_to": "file", "plot_type": "scatter", "projection": "polar", "title": f"Skyplot for {enums.gnss_id_to_name[sys]}\n Azimuth [deg] / Elevation[deg]", "xlim": [0, 2 * np.pi], "ylim": [0, 90], "yticks": (range(0, 90, 30)), # sets 3 concentric circles "yticklabels": (map(str, range(90, 0, -30)) ), # reverse labels from zenith distance to elevation }, ) return figure_paths
def plot_satellite_elevation( self, figure_name: str = "plot_satellite_elevation_{system}.{FIGURE_FORMAT}", ) -> List[pathlib.PosixPath]: """Plot satellite elevation for each GNSS Args: figure_name: File name of figure. Returns: List with figure path for skyplot depending on GNSS. File name ends with GNSS identifier (e.g. 'E', 'G'), for example 'plot_skyplot_E.png'. """ figure_paths = list() # Convert elevation from radian to degree elevation = np.rad2deg(self.dset.site_pos.elevation) # Limit x-axis range to rundate day_start, day_end = self._get_day_limits() # Generate x- and y-axis data per system for sys in sorted(self.dset.unique("system")): x_arrays = [] y_arrays = [] labels = [] figure_path = self.figure_dir / figure_name.replace( "{system}", sys).replace("{FIGURE_FORMAT}", FIGURE_FORMAT) figure_paths.append(figure_path) for sat in sorted(self.dset.unique("satellite")): if not sat.startswith(sys): continue idx = self.dset.filter(satellite=sat) x_arrays.append(self.dset.time.gps.datetime[idx]) y_arrays.append(elevation[idx]) labels.append(sat) # Plot with scatter plot plot( x_arrays=x_arrays, y_arrays=y_arrays, xlabel="Time [GPS]", ylabel="Elevation [deg]", y_unit="", labels=labels, figure_path=figure_path, opt_args={ "colormap": "hsv", "figsize": (7, 8), "legend": True, "legend_ncol": 6, "legend_location": "bottom", "plot_to": "file", "plot_type": "scatter", "title": f"Satellite elevation for {enums.gnss_id_to_name[sys]}", "xlim": [day_start, day_end], }, ) return figure_paths
def _plot_velocity_error( dfs_day: Dict[str, pd.core.frame.DataFrame], dfs_month: Dict[str, pd.core.frame.DataFrame], figure_dir: "pathlib.PosixPath", file_vars: Dict[str, Any], ) -> None: """Plot 2D and 3D velocity error plots (95th percentile) Args: dfs_day: Dictionary with fields as keys (e.g. site_vel_h, site_vel_3d) and the belonging dataframe as value with DAILY samples of 95th percentile and stations as columns. dfs_month: Dictionary with fields as keys (e.g. hpe, vpe) and the belonging dataframe as value with MONTHLY samples of 95th percentile and stations as columns. figure_dir: Figure directory """ ylabel = { "site_vel_h": "2D VE 95%", "site_vel_3d": "3D VE 95%", } opt_args = { "colormap": "tab20", "figsize": (7, 3), # "grid": True, "marker": "o", "markersize": "4", "linestyle": "solid", "plot_to": "file", "plot_type": "plot", # "statistic": ["rms", "mean", "std", "min", "max", "percentile"], #TODO: Is only shown for data, which are plotted at last. "title": file_vars["solution"].upper(), } colors = (config.tech.gnss_vel_comparison_report.colors.list if config.tech.gnss_vel_comparison_report.colors.list else ["orange", "red", "violet", "blue", "green"]) colors = (config.tech.gnss_vel_comparison_report.colors.list if config.tech.gnss_vel_comparison_report.colors.list else ["orange", "red", "violet", "blue", "green"]) # Loop over sampled data samples = {"daily": dfs_day, "monthly": dfs_month} for sample_name, sample_data in samples.items(): # Loop over fields to plot for field in ["site_vel_h", "site_vel_3d"]: if field == "site_vel_h": opt_args["ylim"] = [0.0, 0.03] elif field == "site_vel_3d": opt_args["ylim"] = [0.0, 0.07] # Generate x- and y-arrays for plotting x_arrays = [] y_arrays = [] labels = [] for station in sample_data[field].columns: # if sample_name == "monthly": # opt_args.update({"xlim": "auto", "ylim": [0.0, 3.0]}) x_data = (sample_data[field].index.to_pydatetime() if isinstance(sample_data[field].index, pd.core.indexes.datetimes.DatetimeIndex) else sample_data[field].index) x_arrays.append(list(x_data)) y_arrays.append(list(sample_data[field][station])) labels.append(station.upper()) # Generate plot plot( x_arrays=x_arrays, y_arrays=y_arrays, xlabel="Time [GPS]", ylabel=f"{ylabel[field]}", y_unit="m/s", labels=labels, colors=colors, figure_path=figure_dir / f"plot_{field}_{sample_name}_{file_vars['date']}_{file_vars['solution'].lower()}.{FIGURE_FORMAT}", opt_args=opt_args, )
def _plot_position_error( dfs_day: Dict[str, pd.core.frame.DataFrame], dfs_month: Dict[str, pd.core.frame.DataFrame], figure_dir: PosixPath, file_vars: Dict[str, Any], ) -> None: """Plot horizontal and vertical position error plots Args: dfs_day: Dictionary with function type as keys ('mean', 'percentile', 'rms', 'std') and a dictionary as values. The dictionary has fields as keys (e.g. hpe, vpe) and the belonging dataframe as value with DAILY samples of 95th percentile and stations as columns. dfs_month Dictionary with function type as keys ('mean', 'percentile', 'rms', 'std') and a dictionary as values. The dictionary has fields as keys (e.g. hpe, vpe) and the belonging dataframe as value with MONTHLY samples of 95th percentile and stations as columns. figure_dir: Figure directory """ ylabel_def = { "mean": "MEAN", "percentile": "95%", "rms": "RMS", "std": "STD", } opt_args = { "colormap": "tab20", "figsize": (7, 3), # "grid": True, "marker": "o", "markersize": "4", "linestyle": "solid", "plot_to": "file", "plot_type": "plot", # "statistic": ["rms", "mean", "std", "min", "max", "percentile"], #TODO: Is only shown for data, which are plotted at last. "title": config.tech.gnss_comparison_report.title.str.upper(), } colors = (config.tech.gnss_comparison_report.colors.list if config.tech.gnss_comparison_report.colors.list else ["orange", "red", "violet", "blue", "green"]) colors = (config.tech.gnss_comparison_report.colors.list if config.tech.gnss_comparison_report.colors.list else ["orange", "red", "violet", "blue", "green"]) # Loop over statistical solutions for type_ in dfs_day.keys(): # Get used samples samples = dict() for sample in config.tech.gnss_comparison_report.samples.list: if "daily" == sample: samples["daily"] = dfs_day[type_] elif "monthly" == sample: samples["monthly"] = dfs_month[type_] else: log.fatal( f"Sample '{sample}' is not defined. Only 'daily' and/or 'monthly' can be chosen as sample." ) # Loop over sampled data for sample, sample_data in samples.items(): # Loop over fields to plot for field in [ "east", "north", "up", "hpe", "vpe", "pos_3d", "pdop", "hdop", "vdop" ]: # Get y-range limits if field == "hpe": ylim = config.tech.gnss_comparison_report.ylim_hpe.list elif field == "vpe": ylim = config.tech.gnss_comparison_report.ylim_vpe.list elif field == "pos_3d": ylim = config.tech.gnss_comparison_report.ylim_pos_3d.list else: ylim = config.tech.gnss_comparison_report.ylim.list opt_args["ylim"] = [float(ylim[0]), float(ylim[1])] if ylim else ylim # Generate x- and y-arrays for plotting x_arrays = [] y_arrays = [] labels = [] for station in sample_data[field].columns: #if sample == "monthly": # opt_args.update({"xlim": "auto", "ylim": "auto"}) x_arrays.append(list(sample_data[field].index)) y_arrays.append(list(sample_data[field][station])) labels.append(station.upper()) # Generate plot plot( x_arrays=x_arrays, y_arrays=y_arrays, xlabel="Time [GPS]", ylabel=f"3D {ylabel_def[type_]}" if field == "pos_3d" else f"{field.upper()} {ylabel_def[type_]}", y_unit="m", labels=labels, colors=colors, figure_path=figure_dir / f"plot_{type_}_{field}_{sample}_{file_vars['date']}_{file_vars['solution'].lower()}.{FIGURE_FORMAT}", opt_args=opt_args, )
def _plot_satellite_overview( dset: "Dataset", figure_dir: "pathlib.PosixPath") -> Union[None, Enum]: """Plot satellite observation overview Args: dset: A dataset containing the data. figure_dir: Figure directory Returns: Error exit status if necessary datasets could not be read """ figure_path = figure_dir / f"plot_satellite_overview.{FIGURE_FORMAT}" # Limit x-axis range to rundate day_start, day_end = _get_day_limits(dset) # Get time and satellite data from read and orbit stage file_vars = {**dset.vars, **dset.analysis} file_vars["stage"] = "read" file_path = config.files.path("dataset", file_vars=file_vars) if file_path.exists(): time_read, satellite_read = _sort_by_satellite( _get_dataset(dset, stage="read", systems=dset.meta["obstypes"].keys())) time_orbit, satellite_orbit = _sort_by_satellite( _get_dataset(dset, stage="orbit", systems=dset.meta["obstypes"].keys())) time_edit, satellite_edit = _sort_by_satellite( _get_dataset(dset, stage="edit", systems=dset.meta["obstypes"].keys())) else: # NOTE: This is the case for concatencated Datasets, where "read" and "edit" stage data are not available. log.warn( f"Read dataset does not exists: {file_path}. Plot {figure_path} can not be plotted." ) return enums.ExitStatus.error # Generate plot plot( x_arrays=[time_read, time_orbit, time_edit], y_arrays=[satellite_read, satellite_orbit, satellite_edit], xlabel="Time [GPS]", ylabel="Satellite", y_unit="", # labels = ["Rejected in orbit stage", "Rejected in edit stage", "Kept observations"], colors=["red", "orange", "green"], figure_path=figure_path, opt_args={ "colormap": "tab20", "figsize": (7, 6), "marker": "|", "plot_to": "file", "plot_type": "scatter", "title": "Overview over satellites", "xlim": [day_start, day_end], }, )
def _plot_gnss_signal_in_space_status(dset: "Dataset", figure_dir: "pathlib.PosixPath") -> None: """Generate GNSS Signal-in-Space (SIS) status plot based on SIS status given in RINEX navigation file The SIS status can be: | CODE | SIS STATUS | PLOTTED COLOR | DESCRIPTION | |------|-----------------|---------------|---------------------------------| | 0 | healthy | green | SIS status used by all GNSS | | 1 | marginal (SISA) | yellow | SIS status only used by Galileo | | 2 | marignal (DVS) | orange | SIS status only used by Galileo | | 3 | unhealthy | red | SIS status used by all GNSS | Args: dset: A dataset containing the data. figure_dir: Figure directory """ colors = ["green", "yellow", "orange", "red"] labels = ["healthy", "marginal (sisa)", "marginal (dvs)", "unhealthy"] status_def = [0, 1, 2, 3] signal = None # Select only one Galileo signal # Note: Navigation message for Galileo can include I/NAV and F/NAV messages for different signals (E1, E5a, E5b). # For plotting we choose only one of them. if "E" in dset.unique("system"): signal, _ = _get_first_galileo_signal(dset) # Generate time and satellite data for given SIS status x_arrays = [] y_arrays = [] for status in status_def: time, satellite = _get_gnss_signal_in_space_status_data( dset, status, signal) x_arrays.append(time) y_arrays.append(satellite) # Limit x-axis range to rundate day_start, day_end = _get_day_limits(dset) # Generate plot plot( x_arrays=x_arrays, y_arrays=y_arrays, xlabel="Time [GPS]", ylabel="Satellite", y_unit="", labels=labels, colors=colors, figure_path=figure_dir / f"plot_gnss_signal_in_space_status.{FIGURE_FORMAT}", opt_args={ "figsize": (7, 11), "marker": "s", "marksersize": 10, "legend_ncol": 4, "legend_location": "bottom", "plot_to": "file", "plot_type": "scatter", "tick_labelsize": ("y", 7), # specify labelsize 7 for y-axis "title": f"GNSS signal-in-space status", "xlim": [day_start, day_end], }, )
def _plot_galileo_signal_in_space_status( dset: "Dataset", figure_dir: "pathlib.PosixPath") -> None: """Generate Galileo Signal-in-Space (SIS) status plot based on Galileo signal health status (SHS), SIS Accuracy (SISA) and data validity status (DVS) given in RINEX navigation file. The SIS status can be: | CODE | SIS STATUS | PLOTTED COLOR | DESCRIPTION | |------|-----------------|---------------|---------------------------------| | 0 | healthy | green | SIS status used by all GNSS | | 1 | marginal (SISA) | yellow | SIS status only used by Galileo | | 2 | marignal (DVS) | orange | SIS status only used by Galileo | | 3 | unhealthy | red | SIS status used by all GNSS | Args: dset: A dataset containing the data. figure_dir: Figure directory. """ colors = ["green", "yellow", "orange", "red"] labels = ["healthy", "marginal (sisa)", "marginal (dvs)", "unhealthy"] status_def = [0, 1, 2, 3] signals = _select_galileo_signal(dset) # Generate plot for each given Galileo signal (e.g. E1, E5a, E5b) for signal, nav_type in sorted(signals.items()): x_arrays = [] y_arrays = [] for status in status_def: time, satellite = _get_gnss_signal_in_space_status_data( dset, status, signal, only_galileo=True) x_arrays.append(time) y_arrays.append(satellite) # Limit x-axis range to rundate day_start, day_end = _get_day_limits(dset) # Generate plot plot( x_arrays=x_arrays, y_arrays=y_arrays, xlabel="Time [GPS]", ylabel="Satellite", y_unit="", labels=labels, colors=colors, figure_path=figure_dir / f"plot_galileo_signal_in_space_status_{signal}.{FIGURE_FORMAT}", opt_args={ "figsize": (7, 5), "marker": "s", "marksersize": 10, "legend_ncol": 4, "legend_location": "bottom", "plot_to": "file", "plot_type": "scatter", "title": f"Galileo signal-in-space status for signal {signal.upper()} ({nav_type})", "xlim": [day_start, day_end], }, )