Romulo plot

Romulo plots are heatmaps designed to give a quick overview of CML (commercial microwave link) variables across time and sublinks — useful to spot attenuation bursts, sensor issues or systematic patterns - and to compare them with gauge data.

Romulo for CML

sel_time = {"time": slice("2019-07-17", "2019-07-27")}
cml = open_cml_sample().sel(**sel_time)
att = cml["tsl_avg"] - cml["rsl_avg"]
att
<xarray.DataArray (cml_id: 126, sublink_id: 6, time: 1052)> Size: 6MB
57.5 57.6 57.6 57.6 57.6 57.7 57.7 57.7 57.7 ... nan nan nan nan nan nan nan nan
Coordinates: (10)
Attributes:
    units:    dBm

The OpenSense standard defines three dimensions. However, Romulo plots are two-dimensional. For this reason, we need to reduce the data from three dimensions to two. This can be done by combining the cml_id and sublink_id values to create a single dimension.

Data is sorted by length because, for equal frequency and polarization, \(A_{rain} \propto length\).


romulo_plot_cml


def romulo_plot_cml(
    cml:DataArray, # CML data to plot
    title:str=None, # Title for the plot
    fig:Figure=None, ax:Axes=None, figsize:tuple=(16, 9), # Figure size, only used if fig or ax are None
    vmax:float | None=None, # Maximum value for colormap
)->tuple:

Plot an overview of the CML data sorted by length. Negative values, which are not phisically possible are shown in red.

romulo_plot_cml(att, "Average attenuation");

In order to plot negative data (for example RSL) you can do:

romulo_plot_cml(-cml["rsl_avg"], "Inverted Average Received Signal Level");

Romulo for gauges

gauges = open_gauge_sample().sel(**sel_time)

romulo_plot_gauges


def romulo_plot_gauges(
    gauges:DataArray, # Gauge data to plot
    fig:Figure=None, ax:Axes=None, figsize:tuple=(12, 2), # Figure size, only used if fig or ax are None
    vmax:float=4, # Maximum value for colormap
)->tuple:

Plot data as a heatmap with grid lines separating each gauge.

The following error will be displayed if we try to call the function using the source data.

Code
try:
    romulo_plot_gauges(gauges["rainfall_amount"]);
except IndexError as e:
    print(e)
Irregular time index detected: pcolormesh/imshow stretch cells across gaps, producing misleading plots. 
        Please regularize the time index before plotting:
        - e.g. `da.reindex(time=pd.date_range(..., freq='5min'))`: inserts NaN at missing steps.  Use when timestamps already 
            align to a fixed grid/sampling rate but some are absent.
        - e.g. `da.resample(time='5min').sum(min_count=1)`: aggregates values into regular bins. Use when timestamps don't fall 
            on a fixed grid/sampling rate.

Let’s therefore regularise the dataset and plot the results. As our sampling rate is regular (every five minutes), we can use reindexing. In the plot, red values indicate invalid data (negative rain), while black values show missing data.

regularized = gauges.reindex(time=pd.date_range(gauges.time.min().values, gauges.time.max().values, freq="5min"))
romulo_plot_gauges(regularized["rainfall_amount"]);

Or only a single gauge

romulo_plot_gauges(regularized["rainfall_amount"].isel(id=slice(0, 1)), figsize=(12, 0.5));

CML and Gauges together


romulo_plot


def romulo_plot(
    cml:DataArray, # CML raw or derived data in OpenSense standard
    gauges:DataArray, # Rain gauges data as 2D data array
    title:str | None=None, figsize:tuple=(16, 9), ratio:int=4, # Ratio of the CML plot vs Gauges plot
    cml_vmax:float | None=None, # Maximum value for CML colormap
    gauges_vmax:float=4, # Maximum value for Gauges colormap
)->tuple:

Call self as a function.

romulo_plot(att, regularized["rainfall_amount"], title="Attenuation and rainfall in Douala");