diff --git a/aerpaw/results/plotRadioLogs.m b/aerpaw/results/plotRadioLogs.m index 5a59d94..7371d73 100644 --- a/aerpaw/results/plotRadioLogs.m +++ b/aerpaw/results/plotRadioLogs.m @@ -1,9 +1,12 @@ -function [f, R] = plotRadioLogs(resultsPath) +function [f, fDist, R] = plotRadioLogs(resultsPath, G, tLim) arguments (Input) resultsPath (1, 1) string; + G cell = {}; + tLim (1, 2) datetime = [datetime(-Inf, 'ConvertFrom', 'datenum'), datetime(Inf, 'ConvertFrom', 'datenum')]; end arguments (Output) f (1, 1) matlab.ui.Figure; + fDist (1, 1) matlab.ui.Figure; R cell; end @@ -41,6 +44,7 @@ function [f, R] = plotRadioLogs(resultsPath) metricNames = ["SNR", "Power", "Quality", "PathLoss", "NoiseFloor", "FreqOffset"]; yLabels = ["SNR (dB)", "Power (dB)", "Quality", "Path Loss (dB)", "Noise Floor (dB)", "Freq Offset (MHz)"]; + % --- Time-based figure --- f = figure; tl = tiledlayout(numel(metricNames), 1, 'TileSpacing', 'compact', 'Padding', 'compact'); @@ -57,10 +61,10 @@ function [f, R] = plotRadioLogs(resultsPath) for ti = 1:numel(txIDs) txID = txIDs(ti); rows = tbl(tbl.TxUAVID == txID, :); + rows = rows(rows.Timestamp >= tLim(1) & rows.Timestamp <= tLim(2), :); vals = rows.(metricNames(mi)); - % Skip if all NaN for this metric - if all(isnan(vals)) + if isempty(rows) || all(isnan(vals)) continue; end @@ -81,4 +85,100 @@ function [f, R] = plotRadioLogs(resultsPath) end title(tl, 'Radio Channel Metrics'); -end \ No newline at end of file + + % --- Distance-based figure --- + fDist = figure; + + if isempty(G) + return; + end + + tl2 = tiledlayout(numel(metricNames), 1, 'TileSpacing', 'compact', 'Padding', 'compact'); + + for mi = 1:numel(metricNames) + ax = nexttile(tl2); + hold(ax, 'on'); + grid(ax, 'on'); + + legendEntries = string.empty; + ci = 1; + for rxIdx = 1:nUAV + tbl = R{rxIdx}; + txIDs = unique(tbl.TxUAVID); + for ti = 1:numel(txIDs) + txID = txIDs(ti); + rows = tbl(tbl.TxUAVID == txID, :); + + if isempty(rows) + continue; + end + + rows = rows(rows.Timestamp >= tLim(1) & rows.Timestamp <= tLim(2), :); + if isempty(rows) + continue; + end + + vals = rows.(metricNames(mi)); + if all(isnan(vals)) + continue; + end + + % Map 0-based UAV IDs to 1-based GPS cell indices + txGpsIdx = double(txID) + 1; + rxGpsIdx = double(rows.RxUAVID(1)) + 1; + + if txGpsIdx > numel(G) || rxGpsIdx > numel(G) + continue; + end + + Gtx = G{txGpsIdx}; + Grx = G{rxGpsIdx}; + + if ~ismember('East', Gtx.Properties.VariableNames) || ... + ~ismember('East', Grx.Properties.VariableNames) + continue; + end + + % Strip timezone before posixtime so radio and GPS timestamps + % are treated on the same scale (both are AERPAW wall-clock time) + txTs = Gtx.Timestamp; txTs.TimeZone = ''; + rxTs = Grx.Timestamp; rxTs.TimeZone = ''; + txPt = posixtime(txTs); + rxPt = posixtime(rxTs); + radioPt = posixtime(rows.Timestamp); + + % Interpolate GPS positions at radio measurement times. + % Exclude NaN ENU entries (outside algorithm flight range). + validTx = ~isnan(Gtx.East); + validRx = ~isnan(Grx.East); + + txE = interp1(txPt(validTx), Gtx.East(validTx), radioPt, 'linear', NaN); + txN = interp1(txPt(validTx), Gtx.North(validTx), radioPt, 'linear', NaN); + txU = interp1(txPt(validTx), Gtx.Up(validTx), radioPt, 'linear', NaN); + rxE = interp1(rxPt(validRx), Grx.East(validRx), radioPt, 'linear', NaN); + rxN = interp1(rxPt(validRx), Grx.North(validRx), radioPt, 'linear', NaN); + rxU = interp1(rxPt(validRx), Grx.Up(validRx), radioPt, 'linear', NaN); + + dist = vecnorm([txE - rxE, txN - rxN, txU - rxU], 2, 2); + + if all(isnan(dist)) + continue; + end + + si = mod(ci - 1, numel(styles)) + 1; + scatter(ax, dist, vals, 9, colors(ci, :), strrep(styles(si), "-", ""), 'filled'); + legendEntries(end+1) = sprintf("TX %d → RX %d", txID, rows.RxUAVID(1)); %#ok + ci = ci + 1; + end + end + + ylabel(ax, yLabels(mi)); + if mi == numel(metricNames) + xlabel(ax, 'Distance (m)'); + end + legend(ax, legendEntries, 'Location', 'best'); + hold(ax, 'off'); + end + + title(tl2, 'Radio Channel Metrics vs Distance'); +end diff --git a/aerpaw/results/resultsAnalysis.m b/aerpaw/results/resultsAnalysis.m index 96e5f45..2960bd1 100644 --- a/aerpaw/results/resultsAnalysis.m +++ b/aerpaw/results/resultsAnalysis.m @@ -26,9 +26,8 @@ seaToGroundLevel = 110; % measured approximately from USGS national map viewer plotWholeFlight = true; % do not attempt to automatically trim initial and final positioning and landing from flight plot (buggy) [fGlobe, G] = plotGpsLogs(resultsPath, seaToGroundLevel, true); -% Plot radio statistics -[fRadio, R] = plotRadioLogs(resultsPath); -set(findobj(fRadio, 'Type', 'axes'), 'XLim', controller.timestamp([1, end])); +% Plot radio statistics (time-based and distance-based) +[fRadio, fRadioDist, R] = plotRadioLogs(resultsPath, G, controller.timestamp([1, end])); %% Run simulation % Run miSim using same AERPAW scenario definition CSV