diff --git a/templates/adsb_dashboard.html b/templates/adsb_dashboard.html index 965dcc7..abe68c3 100644 --- a/templates/adsb_dashboard.html +++ b/templates/adsb_dashboard.html @@ -595,6 +595,15 @@
Avg Alt (ft)
+
+ + +
@@ -640,6 +649,84 @@ let selectedIcao = null; let eventSource = null; let isTracking = false; + let currentFilter = 'all'; + + // Military aircraft detection + const MILITARY_RANGES = [ + { start: 0xADF7C0, end: 0xADFFFF, country: 'US' }, + { start: 0xAE0000, end: 0xAEFFFF, country: 'US' }, + { start: 0x3F0000, end: 0x3FFFFF, country: 'FR' }, + { start: 0x400000, end: 0x43FFFF, country: 'UK' }, + { start: 0x43C000, end: 0x43CFFF, country: 'UK' }, + { start: 0x4B0000, end: 0x4B7FFF, country: 'DE' }, + { start: 0x501C00, end: 0x501FFF, country: 'NATO' }, + ]; + + const MILITARY_PREFIXES = [ + 'REACH', 'JAKE', 'DOOM', 'IRON', 'HAWK', 'VIPER', 'COBRA', 'THUNDER', + 'SHADOW', 'NIGHT', 'STEEL', 'GRIM', 'REAPER', 'BLADE', 'STRIKE', + 'RCH', 'CNV', 'MCH', 'EVAC', 'TOPCAT', 'ASCOT', 'RRR', 'HRK', + 'NAVY', 'ARMY', 'USAF', 'RAF', 'RCAF', 'RAAF', 'IAF', 'PAF' + ]; + + const SQUAWK_CODES = { + '7500': { type: 'hijack', name: 'HIJACK' }, + '7600': { type: 'radio', name: 'RADIO FAILURE' }, + '7700': { type: 'mayday', name: 'EMERGENCY' } + }; + + function isMilitaryAircraft(icao, callsign) { + const icaoNum = parseInt(icao, 16); + for (const range of MILITARY_RANGES) { + if (icaoNum >= range.start && icaoNum <= range.end) { + return { military: true, country: range.country }; + } + } + if (callsign) { + const upper = callsign.toUpperCase(); + for (const prefix of MILITARY_PREFIXES) { + if (upper.startsWith(prefix)) { + return { military: true, type: 'callsign' }; + } + } + } + return { military: false }; + } + + function checkSquawkCode(aircraft) { + if (aircraft.squawk && SQUAWK_CODES[aircraft.squawk]) { + return SQUAWK_CODES[aircraft.squawk]; + } + return null; + } + + function applyFilter() { + currentFilter = document.getElementById('aircraftFilter').value; + // Clear markers and redraw + Object.keys(markers).forEach(icao => { + radarMap.removeLayer(markers[icao]); + delete markers[icao]; + }); + Object.keys(markerState).forEach(icao => delete markerState[icao]); + // Trigger full UI update + pendingMarkerUpdates.clear(); + Object.keys(aircraft).forEach(icao => { + if (aircraft[icao].lat && aircraft[icao].lon) { + pendingMarkerUpdates.add(icao); + } + }); + scheduleUIUpdate(); + } + + function passesFilter(icao, ac) { + if (currentFilter === 'all') return true; + const militaryInfo = isMilitaryAircraft(icao, ac.callsign); + const squawkInfo = checkSquawkCode(ac); + if (currentFilter === 'military') return militaryInfo.military; + if (currentFilter === 'civil') return !militaryInfo.military; + if (currentFilter === 'emergency') return !!squawkInfo; + return true; + } // Initialize document.addEventListener('DOMContentLoaded', () => { @@ -828,8 +915,19 @@ const ac = aircraft[icao]; if (!ac || !ac.lat || !ac.lon) return; + // Check filter - remove marker if it doesn't pass + if (!passesFilter(icao, ac)) { + if (markers[icao]) { + radarMap.removeLayer(markers[icao]); + delete markers[icao]; + delete markerState[icao]; + } + return; + } + + const militaryInfo = isMilitaryAircraft(icao, ac.callsign); const rotation = Math.round((ac.heading || 0) / 5) * 5; // Round to 5 degrees - const color = getAltitudeColor(ac.altitude); + const color = militaryInfo.military ? '#556b2f' : getAltitudeColor(ac.altitude); const callsign = ac.callsign || icao; const alt = ac.altitude ? ac.altitude + ' ft' : 'N/A'; @@ -875,13 +973,9 @@ function createMarkerIcon(rotation, color) { return L.divIcon({ className: 'aircraft-marker', - html: `
`, + html: ` + + `, iconSize: [24, 24], iconAnchor: [12, 12] }); @@ -917,7 +1011,9 @@ function renderAircraftList() { const container = document.getElementById('aircraftList'); - const sortedAircraft = Object.values(aircraft) + const sortedAircraft = Object.entries(aircraft) + .filter(([icao, ac]) => passesFilter(icao, ac)) + .map(([icao, ac]) => ({ ...ac, icao })) .sort((a, b) => (b.altitude || 0) - (a.altitude || 0)) .slice(0, MAX_AIRCRAFT_DISPLAY); // Limit to prevent DOM explosion @@ -983,10 +1079,13 @@ const alt = ac.altitude ? ac.altitude.toLocaleString() : '---'; const speed = ac.speed || '---'; const heading = ac.heading ? ac.heading + '°' : '---'; + const militaryInfo = isMilitaryAircraft(ac.icao, ac.callsign); + const militaryBadge = militaryInfo.military ? + `MIL` : ''; return `
- ${callsign} + ${callsign}${militaryBadge} ${ac.icao}
@@ -1040,9 +1139,13 @@ const heading = ac.heading ? ac.heading + '°' : 'N/A'; const squawk = ac.squawk || 'N/A'; const vRate = ac.vRate ? (ac.vRate > 0 ? '+' : '') + ac.vRate + ' ft/min' : 'N/A'; + const militaryInfo = isMilitaryAircraft(ac.icao, ac.callsign); + const militaryBadge = militaryInfo.military ? + `
MILITARY AIRCRAFT${militaryInfo.country ? ' (' + militaryInfo.country + ')' : ''}
` : ''; container.innerHTML = `
${callsign}
+ ${militaryBadge}
ICAO
diff --git a/templates/index.html b/templates/index.html index dd42b8a..9c84359 100644 --- a/templates/index.html +++ b/templates/index.html @@ -3193,6 +3193,15 @@ Cluster Markers
+
+ + +
@@ -7688,6 +7697,27 @@ } } + function applyAircraftFilter() { + // Clear all markers and redraw with new filter + Object.keys(aircraftMarkers).forEach(icao => { + if (clusteringEnabled && aircraftClusterGroup) { + aircraftClusterGroup.removeLayer(aircraftMarkers[icao]); + } else if (aircraftMap) { + aircraftMap.removeLayer(aircraftMarkers[icao]); + } + delete aircraftMarkers[icao]; + delete aircraftMarkerState[icao]; + }); + // Trail lines should also be cleared for filtered-out aircraft + Object.keys(aircraftTrailLines).forEach(icao => { + if (aircraftMap) { + aircraftMap.removeLayer(aircraftTrailLines[icao]); + } + delete aircraftTrailLines[icao]; + }); + updateAircraftMarkers(); + } + function updateRadarTime() { const now = new Date(); const time = now.toTimeString().substring(0, 8); @@ -7762,11 +7792,21 @@ const showLabels = document.getElementById('adsbShowLabels')?.checked; const showAltitude = document.getElementById('adsbShowAltitude')?.checked; const showTrails = document.getElementById('adsbShowTrails')?.checked ?? true; + const aircraftFilter = document.getElementById('adsbAircraftFilter')?.value || 'all'; const currentIds = new Set(); // Sort aircraft by altitude and limit to prevent DOM explosion const sortedAircraft = Object.entries(adsbAircraft) .filter(([_, a]) => a.lat != null && a.lon != null) + .filter(([icao, a]) => { + if (aircraftFilter === 'all') return true; + const militaryInfo = isMilitaryAircraft(icao, a.callsign); + const squawkInfo = checkSquawkCode(a); + if (aircraftFilter === 'military') return militaryInfo.military; + if (aircraftFilter === 'civil') return !militaryInfo.military; + if (aircraftFilter === 'emergency') return !!squawkInfo; + return true; + }) .sort((a, b) => (b[1].altitude || 0) - (a[1].altitude || 0)) .slice(0, MAX_AIRCRAFT_MARKERS);