Add historical flight track functionality

- Store track history with position, altitude, speed, and timestamp
- Automatic track point collection every 30 seconds when position changes
- API endpoint /api/aircraft/{hex}/history for individual aircraft tracks
- Frontend "Show History" button to display historical flight paths
- Click aircraft markers to show their historical track (dashed red line)
- Track cleanup: keep last 200 points per aircraft, 24-hour retention
- Add aircraft type badges in table view with color coding
- Start/end markers for historical tracks with timestamps

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Ole-Morten Duesund 2025-08-23 22:52:16 +02:00
commit 55710614da
6 changed files with 215 additions and 16 deletions

View file

@ -213,6 +213,20 @@ body {
background: #404040;
}
.type-badge {
padding: 0.2rem 0.4rem;
border-radius: 3px;
font-size: 0.7rem;
font-weight: bold;
color: #000000;
}
.type-badge.commercial { background: #00ff88; }
.type-badge.cargo { background: #ff8c00; }
.type-badge.military { background: #ff4444; }
.type-badge.ga { background: #ffff00; }
.type-badge.ground { background: #888888; color: #ffffff; }
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));

View file

@ -30,6 +30,7 @@
<div class="map-controls">
<button id="center-map">Center Map</button>
<button id="toggle-trails">Toggle Trails</button>
<button id="toggle-history">Show History</button>
</div>
<div class="legend">
@ -73,6 +74,7 @@
<tr>
<th>Flight</th>
<th>Hex</th>
<th>Type</th>
<th>Altitude</th>
<th>Speed</th>
<th>Track</th>

View file

@ -3,9 +3,11 @@ class SkyView {
this.map = null;
this.aircraftMarkers = new Map();
this.aircraftTrails = new Map();
this.historicalTracks = new Map();
this.websocket = null;
this.aircraftData = [];
this.showTrails = false;
this.showHistoricalTracks = false;
this.currentView = 'map';
this.charts = {};
this.origin = { latitude: 37.7749, longitude: -122.4194, name: 'Default' };
@ -95,6 +97,9 @@ class SkyView {
const trailsBtn = document.getElementById('toggle-trails');
trailsBtn.addEventListener('click', () => this.toggleTrails());
const historyBtn = document.getElementById('toggle-history');
historyBtn.addEventListener('click', () => this.toggleHistoricalTracks());
}
initializeWebSocket() {
@ -220,6 +225,10 @@ class SkyView {
if (this.showTrails) {
this.updateTrail(aircraft.hex, pos);
}
if (this.showHistoricalTracks && aircraft.track_history && aircraft.track_history.length > 1) {
this.displayHistoricalTrack(aircraft.hex, aircraft.track_history, aircraft.flight);
}
});
}
@ -239,6 +248,12 @@ class SkyView {
marker.bindPopup(this.createPopupContent(aircraft), {
className: 'aircraft-popup'
});
marker.on('click', () => {
if (this.showHistoricalTracks) {
this.loadAircraftHistory(aircraft.hex);
}
});
return marker;
}
@ -409,10 +424,14 @@ class SkyView {
this.sortAircraft(filteredData, sortBy);
filteredData.forEach(aircraft => {
const type = this.getAircraftType(aircraft);
const typeDisplay = type.charAt(0).toUpperCase() + type.slice(1);
const row = document.createElement('tr');
row.innerHTML = `
<td>${aircraft.flight || '-'}</td>
<td>${aircraft.hex}</td>
<td><span class="type-badge ${type}">${typeDisplay}</span></td>
<td>${aircraft.alt_baro || '-'}</td>
<td>${aircraft.gs || '-'}</td>
<td>${aircraft.track || '-'}</td>
@ -578,6 +597,88 @@ class SkyView {
console.error('Failed to fetch stats:', error);
}
}
toggleHistoricalTracks() {
this.showHistoricalTracks = !this.showHistoricalTracks;
const btn = document.getElementById('toggle-history');
btn.textContent = this.showHistoricalTracks ? 'Hide History' : 'Show History';
if (!this.showHistoricalTracks) {
this.clearAllHistoricalTracks();
}
}
async loadAircraftHistory(hex) {
try {
const response = await fetch(`/api/aircraft/${hex}/history`);
const data = await response.json();
if (data.track_history && data.track_history.length > 1) {
this.displayHistoricalTrack(hex, data.track_history, data.flight);
}
} catch (error) {
console.error('Failed to load aircraft history:', error);
}
}
displayHistoricalTrack(hex, trackHistory, flight) {
this.clearHistoricalTrack(hex);
const points = trackHistory.map(point => [point.lat, point.lon]);
const polyline = L.polyline(points, {
color: '#ff6b6b',
weight: 3,
opacity: 0.8,
dashArray: '5, 5'
}).addTo(this.map);
polyline.bindPopup(`<b>Historical Track</b><br>${flight || hex}<br>${trackHistory.length} points`);
this.historicalTracks.set(hex, polyline);
// Add start/end markers
if (trackHistory.length > 0) {
const start = trackHistory[0];
const end = trackHistory[trackHistory.length - 1];
L.circleMarker([start.lat, start.lon], {
color: '#ffffff',
fillColor: '#00ff00',
fillOpacity: 0.8,
radius: 4
}).addTo(this.map).bindPopup(`<b>Start</b><br>${new Date(start.timestamp).toLocaleString()}`);
L.circleMarker([end.lat, end.lon], {
color: '#ffffff',
fillColor: '#ff0000',
fillOpacity: 0.8,
radius: 4
}).addTo(this.map).bindPopup(`<b>End</b><br>${new Date(end.timestamp).toLocaleString()}`);
}
}
clearHistoricalTrack(hex) {
if (this.historicalTracks.has(hex)) {
this.map.removeLayer(this.historicalTracks.get(hex));
this.historicalTracks.delete(hex);
}
}
clearAllHistoricalTracks() {
this.historicalTracks.forEach(track => {
this.map.removeLayer(track);
});
this.historicalTracks.clear();
// Also remove start/end markers
this.map.eachLayer(layer => {
if (layer instanceof L.CircleMarker) {
this.map.removeLayer(layer);
}
});
}
}
document.addEventListener('DOMContentLoaded', () => {