'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 = '
Enter an MMSI and date range, then press LOAD TRACK.
'; 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 = '
Loading…
'; 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 = ``; return; } if (!_dvrTrack.length) { body.innerHTML = '
No track data found for this MMSI and date range.
'; 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 = ` ${preview.map((p, i) => p ? ` ` : '' ).join('')}
#Timestamp (UTC)LATLON SOG (kn)COG (°)
${(i < 50 ? i + 1 : _dvrTrack.length - 99 + i)} ${p.ts?.slice(0, 19)} ${p.lat?.toFixed(5) ?? '--'} ${p.lon?.toFixed(5) ?? '--'} ${p.sog?.toFixed(1) ?? '--'} ${p.cog?.toFixed(0) ?? '--'}
`; } // ── 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'));