Files
AidsMonitoring/frontend/js/dvr.js
T

198 lines
8.7 KiB
JavaScript

'use strict';
// DVR — AIS Track History and Playback
let _dvrTrack = []; // loaded track points
let _dvrTimer = null; // setInterval handle for playback
let _dvrIndex = 0; // current playback position
let _dvrPlaying = false;
// ── Open / init modal ─────────────────────────────────────────────────────────
window.openTrackHistoryModal = function() {
const today = new Date().toISOString().slice(0, 10);
const from = new Date(Date.now() - 7 * 86400000).toISOString().slice(0, 10);
document.getElementById('dvr-from').value = from;
document.getElementById('dvr-to').value = today;
document.getElementById('dvr-mmsi').value = '';
document.getElementById('dvr-body').innerHTML =
'<div style="color:var(--text-muted);font-size:0.75rem;padding:20px 0;text-align:center">Enter an MMSI and date range, then press LOAD TRACK.</div>';
document.getElementById('dvr-stats').style.display = 'none';
document.getElementById('dvr-controls').style.display = 'none';
_dvrStop();
_dvrTrack = [];
_showModal('modal-track-history');
};
// ── Load track ────────────────────────────────────────────────────────────────
document.getElementById('btn-dvr-load')?.addEventListener('click', _dvrLoad);
async function _dvrLoad() {
const mmsi = document.getElementById('dvr-mmsi').value.trim();
const from = document.getElementById('dvr-from').value;
const to = document.getElementById('dvr-to').value;
const source = document.getElementById('dvr-source').value;
if (!mmsi) { alert('Enter an MMSI first.'); return; }
const body = document.getElementById('dvr-body');
body.innerHTML = '<div style="color:var(--text-muted);padding:8px">Loading…</div>';
document.getElementById('dvr-stats').style.display = 'none';
document.getElementById('dvr-controls').style.display = 'none';
_dvrStop();
try {
const endpoint = source === 'aton'
? `${API}/tracks/atons/${encodeURIComponent(mmsi)}`
: `${API}/tracks/vessels/${encodeURIComponent(mmsi)}`;
const url = `${endpoint}?from=${from}T00:00:00&to=${to}T23:59:59&limit=50000`;
const res = await fetch(url, {
headers: { Authorization: `Bearer ${window.Auth?.token()}` }
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
_dvrTrack = await res.json();
} catch (e) {
body.innerHTML = `<div class="modal-error" style="display:block">Error: ${e.message}</div>`;
return;
}
if (!_dvrTrack.length) {
body.innerHTML = '<div style="color:var(--text-muted);font-size:0.75rem;padding:20px 0;text-align:center">No track data found for this MMSI and date range.</div>';
return;
}
// Stats bar
const statsEl = document.getElementById('dvr-stats');
document.getElementById('dvr-stats-text').textContent =
`${_dvrTrack.length} points · from ${_dvrTrack[0].ts.slice(0,19)} to ${_dvrTrack.at(-1).ts.slice(0,19)} UTC`;
statsEl.style.display = '';
// Playback controls
const ctrl = document.getElementById('dvr-controls');
ctrl.style.display = '';
const slider = document.getElementById('dvr-slider');
slider.max = _dvrTrack.length - 1;
slider.value = 0;
_dvrIndex = 0;
_dvrUpdateDisplay(0);
// Table preview (first/last 50)
const preview = _dvrTrack.length > 100
? [..._dvrTrack.slice(0, 50), null, ..._dvrTrack.slice(-50)]
: _dvrTrack;
body.innerHTML = `
<table class="users-table">
<thead><tr>
<th>#</th><th>Timestamp (UTC)</th><th>LAT</th><th>LON</th>
<th>SOG (kn)</th><th>COG (°)</th>
</tr></thead>
<tbody>
${preview.map((p, i) => p ? `
<tr style="font-size:0.7rem">
<td class="mono" style="color:var(--text-muted)">${(i < 50 ? i + 1 : _dvrTrack.length - 99 + i)}</td>
<td class="mono">${p.ts?.slice(0, 19)}</td>
<td class="mono">${p.lat?.toFixed(5) ?? '--'}</td>
<td class="mono">${p.lon?.toFixed(5) ?? '--'}</td>
<td>${p.sog?.toFixed(1) ?? '--'}</td>
<td>${p.cog?.toFixed(0) ?? '--'}</td>
</tr>` : '<tr><td colspan="6" style="text-align:center;color:var(--text-muted);font-size:0.7rem">…</td></tr>'
).join('')}
</tbody>
</table>`;
}
// ── Playback controls ─────────────────────────────────────────────────────────
document.getElementById('btn-dvr-play')?.addEventListener('click', _dvrPlay);
document.getElementById('btn-dvr-pause')?.addEventListener('click', _dvrPause);
document.getElementById('btn-dvr-stop')?.addEventListener('click', _dvrStop);
document.getElementById('dvr-slider')?.addEventListener('input', (e) => {
_dvrIndex = parseInt(e.target.value);
_dvrUpdateDisplay(_dvrIndex);
});
function _dvrPlay() {
if (!_dvrTrack.length) return;
if (_dvrIndex >= _dvrTrack.length - 1) _dvrIndex = 0;
_dvrPlaying = true;
const speed = parseInt(document.getElementById('dvr-speed').value) || 20;
// Each step advances by `speed` seconds of track data; timer fires at 100ms
if (_dvrTimer) clearInterval(_dvrTimer);
_dvrTimer = setInterval(() => {
if (!_dvrPlaying || _dvrIndex >= _dvrTrack.length - 1) {
_dvrPause();
return;
}
// Find next point that is at least `speed` seconds ahead
const curTs = new Date(_dvrTrack[_dvrIndex].ts).getTime();
let next = _dvrIndex + 1;
while (next < _dvrTrack.length - 1) {
const diff = (new Date(_dvrTrack[next].ts).getTime() - curTs) / 1000;
if (diff >= speed) break;
next++;
}
_dvrIndex = next;
document.getElementById('dvr-slider').value = _dvrIndex;
_dvrUpdateDisplay(_dvrIndex);
}, 100);
}
function _dvrPause() {
_dvrPlaying = false;
if (_dvrTimer) { clearInterval(_dvrTimer); _dvrTimer = null; }
}
function _dvrStop() {
_dvrPause();
_dvrIndex = 0;
const slider = document.getElementById('dvr-slider');
if (slider) slider.value = 0;
if (_dvrTrack.length) _dvrUpdateDisplay(0);
// Remove DVR marker from map if present
if (window._dvrMapClear) window._dvrMapClear();
}
function _dvrUpdateDisplay(idx) {
if (!_dvrTrack.length || idx >= _dvrTrack.length) return;
const p = _dvrTrack[idx];
document.getElementById('dvr-timestamp').textContent =
p.ts ? p.ts.slice(0, 19) + ' UTC' : '--';
document.getElementById('dvr-lat').textContent = p.lat ? `LAT ${p.lat.toFixed(5)}` : 'LAT --';
document.getElementById('dvr-lon').textContent = p.lon ? `LON ${p.lon.toFixed(5)}` : 'LON --';
document.getElementById('dvr-sog').textContent = p.sog != null ? `SOG ${p.sog.toFixed(1)}` : 'SOG --';
document.getElementById('dvr-cog').textContent = p.cog != null ? `COG ${p.cog.toFixed(0)}` : 'COG --';
// Move DVR marker on map (map.js must expose window._dvrMapMove)
if (window._dvrMapMove && p.lat != null && p.lon != null) {
window._dvrMapMove(p.lon, p.lat, p.cog ?? 0);
}
}
// ── Show on map ───────────────────────────────────────────────────────────────
document.getElementById('btn-dvr-show-map')?.addEventListener('click', () => {
if (!_dvrTrack.length) return;
if (window._dvrMapShowTrack) {
window._dvrMapShowTrack(_dvrTrack);
_hideModal('modal-track-history');
}
});
// ── Export CSV ────────────────────────────────────────────────────────────────
document.getElementById('btn-dvr-csv')?.addEventListener('click', () => {
if (!_dvrTrack.length) return;
const mmsi = document.getElementById('dvr-mmsi').value.trim();
const rows = ['timestamp,lat,lon,sog,cog,heading',
..._dvrTrack.map(p =>
`${p.ts},${p.lat},${p.lon},${p.sog ?? ''},${p.cog ?? ''},${p.heading ?? ''}`)
];
const blob = new Blob([rows.join('\n')], { type: 'text/csv' });
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = `track_${mmsi}_${document.getElementById('dvr-from').value}.csv`;
a.click();
URL.revokeObjectURL(a.href);
});
// ── Close buttons ─────────────────────────────────────────────────────────────
document.getElementById('btn-close-track-history')?.addEventListener('click', () => _hideModal('modal-track-history'));
document.getElementById('btn-close-track-history2')?.addEventListener('click', () => _hideModal('modal-track-history'));