This commit is contained in:
2026-06-16 11:42:23 +03:00
parent 64607def4a
commit 0e1fa15a2f
3 changed files with 74 additions and 31 deletions
+69 -28
View File
@@ -361,7 +361,7 @@
{ position: 'topright', collapsed: true }
).addTo(map);
const API_BUILD = '2026-06-16c';
const API_BUILD = '2026-06-16d';
const markers = {};
let selectedId = null;
@@ -560,10 +560,13 @@
}
function snapAtCursor(track, telemetryRows, cursor, roleFallback) {
const pos = trackPointAt(track, cursor);
if (pos?.meta && String(pos.meta).length > 2) {
const ts = timelineUseProgress ? null : cursor.t;
return snapFromTrackPoint(pos, ts, roleFallback || track?.role);
}
if (timelineUseProgress) {
const pos = positionAtProgress(track?.points, cursor.progress);
if (!pos) return null;
return { meta: pos.meta, role: roleFallback, rssi: pos.rssi, ts: cursor.progress };
return snapFromTrackPoint(pos, null, roleFallback || track?.role);
}
return snapAtTime(track, telemetryRows, cursor.t, roleFallback);
}
@@ -592,21 +595,42 @@
return { ...track, points };
}
function snapAtTime(track, telemetryRows, t, roleFallback) {
const tel = telemetryAtTime(telemetryRows, t);
if (tel) return tel;
const pos = positionAt(track?.points, t);
function normalizeTelemetry(rows) {
if (!rows?.length) return [];
return rows
.map(r => ({ ...r, ts: Number(r.ts) }))
.sort((a, b) => a.ts - b.ts);
}
function trackPointAt(track, cursor) {
if (!track?.points?.length) return null;
return timelineUseProgress
? positionAtProgress(track.points, cursor.progress)
: positionAt(track.points, cursor.t);
}
function snapFromTrackPoint(pos, t, roleFallback) {
if (!pos) return null;
return {
meta: pos.meta,
role: roleFallback || track?.role,
role: roleFallback,
rssi: pos.rssi,
ts: t,
lat: pos.lat,
lon: pos.lon
lon: pos.lon,
};
}
function snapAtTime(track, telemetryRows, t, roleFallback) {
const pos = positionAt(track?.points, t);
if (pos?.meta && String(pos.meta).length > 2) {
return snapFromTrackPoint(pos, t, roleFallback || track?.role);
}
const tel = telemetryAtTime(telemetryRows, t);
if (tel) return tel;
return snapFromTrackPoint(pos, t, roleFallback || track?.role);
}
function rxQualityFromMeta(meta) {
if (!meta) return null;
const snap = RadioUI.parseRadioSnapshot(meta);
@@ -1847,18 +1871,17 @@
function telemetryAtTime(rows, t) {
if (!rows?.length) return null;
const first = rows[0];
const last = rows[rows.length - 1];
if (t <= first.ts) return first;
if (t >= last.ts) return last;
for (let i = 0; i < rows.length - 1; i++) {
const a = rows[i];
const b = rows[i + 1];
if (t >= a.ts && t <= b.ts) {
return t - a.ts <= b.ts - t ? a : b;
const tNum = Number(t);
let best = null;
let bestD = Infinity;
for (const r of rows) {
const d = Math.abs(Number(r.ts) - tNum);
if (d < bestD) {
best = r;
bestD = d;
}
}
return last;
return best;
}
function telemetryFromTrackPoint(track, t, roleFallback) {
@@ -1890,9 +1913,21 @@
return bestD === 0 ? best : null;
}
function trackMetaDiversity(points) {
const packets = new Set();
let withMeta = 0;
for (const p of points || []) {
if (!p.meta || String(p.meta).length < 3) continue;
withMeta++;
const snap = RadioUI.parseRadioSnapshot(p.meta);
if (snap.packet != null) packets.add(snap.packet);
}
return { withMeta, uniquePackets: packets.size, total: points?.length || 0 };
}
function pairedTelemetryAtTime(txTrack, rxTrack, telemetryTx, telemetryRx, t) {
let txTel = telemetryAtTime(telemetryTx, t) || telemetryFromTrackPoint(txTrack, t, 'TX');
let rxTel = telemetryAtTime(telemetryRx, t) || telemetryFromTrackPoint(rxTrack, t, 'RX');
let txTel = snapAtTime(txTrack, telemetryTx, t, 'TX');
let rxTel = snapAtTime(rxTrack, telemetryRx, t, 'RX');
const txSnap = txTel ? telemetryToSnap(txTel) : null;
const rxSnap = rxTel ? telemetryToSnap(rxTel) : null;
if (txSnap?.packet != null) {
@@ -2211,6 +2246,12 @@
noteText =
'Треки не пересекаются по времени — шкала на полном диапазоне; вне записи позиция удерживается на краю.';
}
const txDiv = trackMetaDiversity(loadedTxTrack.points);
const rxDiv = trackMetaDiversity(loadedRxTrack.points);
if (txDiv.uniquePackets <= 1 && rxDiv.uniquePackets <= 1
&& (txDiv.withMeta > 1 || rxDiv.withMeta > 1)) {
noteText += ' Радио-статистика в точках трека не менялась при записи.';
}
applyTimelineRange(range, noteText);
}
setTimelineVisible(true);
@@ -2227,7 +2268,7 @@
`/api/telemetry?device_id=${encodeURIComponent(loadedSingleTrack.device_id)}&since=${range.min}&until=${range.max}&limit=500`,
{ cache: 'no-store' }
);
if (res.ok) telemetrySingle = await res.json();
if (res.ok) telemetrySingle = normalizeTelemetry(await res.json());
}
updateTimelineAtSingle(timelineCursor());
return;
@@ -2240,8 +2281,8 @@
fetch(`/api/telemetry?device_id=${encodeURIComponent(loadedTxTrack.device_id)}&since=${range.min}&until=${range.max}&limit=500`, { cache: 'no-store' }),
fetch(`/api/telemetry?device_id=${encodeURIComponent(loadedRxTrack.device_id)}&since=${range.min}&until=${range.max}&limit=500`, { cache: 'no-store' })
]);
if (telTx.ok) telemetryTx = await telTx.json();
if (telRx.ok) telemetryRx = await telRx.json();
if (telTx.ok) telemetryTx = normalizeTelemetry(await telTx.json());
if (telRx.ok) telemetryRx = normalizeTelemetry(await telRx.json());
}
updateTimelineAt(timelineCursor());
}
@@ -2326,7 +2367,7 @@
`/api/telemetry?device_id=${encodeURIComponent(loadedSingleTrack.device_id)}&since=${range.min}&until=${range.max}&limit=500`,
{ cache: 'no-store' }
);
if (telRes.ok) telemetrySingle = await telRes.json();
if (telRes.ok) telemetrySingle = normalizeTelemetry(await telRes.json());
updateTimelineAtSingle(timelineCursor());
}
document.getElementById('trackInfo').textContent =
@@ -2387,8 +2428,8 @@
fetch(`/api/telemetry?device_id=${encodeURIComponent(loadedTxTrack.device_id)}&since=${range.min}&until=${range.max}&limit=500`, { cache: 'no-store' }),
fetch(`/api/telemetry?device_id=${encodeURIComponent(loadedRxTrack.device_id)}&since=${range.min}&until=${range.max}&limit=500`, { cache: 'no-store' })
]);
if (telTx.ok) telemetryTx = await telTx.json();
if (telRx.ok) telemetryRx = await telRx.json();
if (telTx.ok) telemetryTx = normalizeTelemetry(await telTx.json());
if (telRx.ok) telemetryRx = normalizeTelemetry(await telRx.json());
updateTimelineAt(timelineCursor());
}
+4 -2
View File
@@ -116,8 +116,10 @@
function renderCompareGrid(txSnap, rxSnap, txId, rxId, changedTx, changedRx, staticOpen) {
let html = '<div class="radio-compare-grid">';
html += `<div class="radio-compare-head"><span class="legend-tx">TX</span> ${escapeHtml(txId || '—')}`;
html += `<span class="legend-rx">RX</span> ${escapeHtml(rxId || '—')}</div>`;
html += '<div class="radio-compare-head">';
html += `<span><span class="legend-tx">TX</span> ${escapeHtml(txId || '—')}</span>`;
html += `<span><span class="legend-rx">RX</span> ${escapeHtml(rxId || '—')}</span>`;
html += '</div>';
for (const row of DYNAMIC_ROWS) {
const txCls = changedTx && changedTx.has(row.key) ? ' changed' : '';
const rxCls = changedRx && changedRx.has(row.key) ? ' changed' : '';