Map: TRAFFIC (TCAS) + NEXRAD overlays — two more dead softkeys now functional

The TRAFFIC and NEXRAD map keys were dead. Now: TRAFFIC draws TCAS diamonds
(threat-coloured other/proximate/TA/RA, relative altitude + climb/descend arrow);
NEXRAD draws green/yellow/red precip cells. Both toggle from the MAP softkeys and
light when active. Driven by values.traffic / values.wxCells, which the demo
synthesizes so they're demonstrable; the live-sim binding (TCAS/weather datarefs)
is the part that still needs a sim session to wire+verify.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-04 02:57:53 +02:00
parent 5f63c5032c
commit a32b5a9b06
4 changed files with 58 additions and 2 deletions
+11
View File
@@ -326,6 +326,17 @@ function startDemo() {
engRpm: [2410], fuelFlow: [0.0072], oilTemp: [88], oilPress: [52], egt: [720],
fuelQty: [60, 58], volts: [process.env.DEMO_ALERT ? 23.4 : 28.0, 27.8], amps: [-1.5], genAmps: [20.5], engHrs: 5040,
fuelTot: 118 * 2.72, fuelMax: 144 * 2.72, // fuel totalizer: 118 of 144 gal (kg) — SYSTEM keys adjust
// TRAFFIC (TCAS) + NEXRAD demo data so those map overlays are demonstrable.
// relAlt = hundreds of ft vs own ship; vs +/- = climb/descend; thr = TA/RA.
traffic: [
{ lat: 47.52, lon: -122.28, relAlt: 12, vs: 1, thr: 0 },
{ lat: 47.40, lon: -122.45, relAlt: -8, vs: -1, thr: 1 },
{ lat: 47.47, lon: -122.18, relAlt: 2, vs: 0, thr: 2 },
],
wxCells: [ // synthetic NEXRAD precip: lat,lon,radiusNm,level(1 green·2 yellow·3 red)
{ lat: 47.7, lon: -122.0, r: 8, lvl: 2 }, { lat: 47.75, lon: -121.9, r: 5, lvl: 3 },
{ lat: 47.2, lon: -122.6, r: 10, lvl: 1 }, { lat: 47.25, lon: -122.5, r: 6, lvl: 2 },
],
});
// a sample plan so the map/FMS show something in demo mode
fp.setPlan({ name: 'DEMO', waypoints: [
+3 -1
View File
@@ -76,6 +76,8 @@ export default function Bezel({ variant = 'pfd', xp, svt3d, onToggleSvt, svtOpts
else if (label === 'DCLTR') cycleDcltr(onMapMode);
else if (label === 'AIRWAYS') onMapMode && onMapMode((m) => ({ ...m, airways: (((m.airways | 0) + 1) % 4) })); // off→all→Victor→Jet
else if (label === 'AIRSPACE') onMapMode && onMapMode((m) => ({ ...m, airspace: !m.airspace }));
else if (label === 'TRAFFIC') onMapMode && onMapMode((m) => ({ ...m, traffic: !m.traffic }));
else if (label === 'NEXRAD') onMapMode && onMapMode((m) => ({ ...m, nexrad: !m.nexrad }));
} else {
if (label === 'PFD') setPage('pfd');
else if (label === 'BACK') setPage({ xpdrcode: 'xpdr', altunit: 'pfd' }[page] || 'root');
@@ -118,7 +120,7 @@ export default function Bezel({ variant = 'pfd', xp, svt3d, onToggleSvt, svtOpts
if (variant === 'mfd') return (label === 'TOPO' && mapMode?.base === 'topo')
|| (label === 'TERRAIN' && mapMode?.terrain) || (label === 'OSM' && mapMode?.base === 'osm')
|| (label === 'DCLTR' && mapMode?.dcltr > 0) || (label === 'AIRWAYS' && mapMode?.airways)
|| (label === 'AIRSPACE' && mapMode?.airspace);
|| (label === 'AIRSPACE' && mapMode?.airspace) || (label === 'TRAFFIC' && mapMode?.traffic) || (label === 'NEXRAD' && mapMode?.nexrad);
return (label === 'SYN TERR' && svt3d) || (label === 'PATHWAY' && svtOpts?.pathway) || (label === 'APTSIGNS' && svtOpts?.aptSigns)
|| (label === 'INSET' && inset) || (label === 'NRST' && nrst) || (label === 'TMR/REF' && tmr)
|| (label === 'DME' && dme) || (label === 'OBS' && obs) || (label === 'CAUTION' && (alerts || hasAlerts))
+40 -1
View File
@@ -57,6 +57,19 @@ const TILES = {
dark: null,
};
// TCAS target symbol: diamond coloured by threat (other/proximate/TA/RA), with
// relative altitude (hundreds of ft, ± vs own ship) and a climb/descend arrow.
const TCAS_COLOR = ['#cfd6dd', '#19d3ff', '#ffce00', '#ff3b3b'];
function tcasSymbol(t) {
const C = TCAS_COLOR[t.thr || 0];
const filled = (t.thr || 0) >= 1;
const arrow = t.vs > 0 ? '▲' : t.vs < 0 ? '▼' : '';
const rel = (t.relAlt >= 0 ? '+' : '') + String(Math.abs(Math.round(t.relAlt))).padStart(2, '0');
const diamond = `<svg viewBox='0 0 16 16' width='16' height='16'><polygon points='8,1 15,8 8,15 1,8' fill='${filled ? C : 'none'}' stroke='${C}' stroke-width='2'/></svg>`;
const html = `<div class='tcas-sym' style='color:${C}'>${diamond}<span class='tcas-lbl'>${rel}${arrow}</span></div>`;
return L.marker([t.lat, t.lon], { icon: L.divIcon({ className: 'tcas-divicon', html, iconSize: [16, 16], iconAnchor: [8, 8] }), interactive: false, zIndexOffset: 1200 });
}
// G1000 / aeronautical-chart airspace styling, keyed by our coarse class. B/C/D
// follow chart convention (B solid blue, C solid magenta, D dashed blue);
// special-use areas use warm hues. Class A/E are omitted (they blanket huge
@@ -88,6 +101,8 @@ export default function MapView({ values, flightPlan, fp, inset = false, hud = t
const aspLayerRef = useRef(null);
const aspOnRef = useRef(false);
const refreshAirspaceRef = useRef(null);
const wxLayerRef = useRef(null);
const trafficLayerRef = useRef(null);
const baseRef = useRef(null);
const terrRef = useRef(null);
const zoomingRef = useRef(false);
@@ -102,6 +117,8 @@ export default function MapView({ values, flightPlan, fp, inset = false, hud = t
const base = mapMode?.base || 'topo';
const airways = (mapMode?.airways | 0); // 0 off · 1 all · 2 Victor (low) · 3 Jet (high)
const airspace = !!mapMode?.airspace;
const traffic = !!mapMode?.traffic;
const nexrad = !!mapMode?.nexrad;
aspOnRef.current = airspace;
const dcltrRef = useRef(dcltr);
dcltrRef.current = dcltr;
@@ -135,11 +152,13 @@ export default function MapView({ values, flightPlan, fp, inset = false, hud = t
map.getPane('terrain').style.zIndex = 250;
map.getPane('terrain').style.pointerEvents = 'none';
aspLayerRef.current = L.layerGroup().addTo(map); // airspace polygons (bottom overlay)
wxLayerRef.current = L.layerGroup().addTo(map); // NEXRAD precip (bottom)
aspLayerRef.current = L.layerGroup().addTo(map); // airspace polygons
awyLayerRef.current = L.layerGroup().addTo(map); // airways
navLayerRef.current = L.layerGroup().addTo(map); // real airports/navaids/fixes
routeRef.current = L.layerGroup().addTo(map); // flight-plan legs (white + magenta active)
wpLayerRef.current = L.layerGroup().addTo(map);
trafficLayerRef.current = L.layerGroup().addTo(map); // TCAS targets (top)
// AIRWAYS overlay (Victor/Jet routes from X-Plane's earth_awy.dat). Light
// blue lines with the airway name at the segment midpoint (labels ≥ z8).
@@ -259,6 +278,26 @@ export default function MapView({ values, flightPlan, fp, inset = false, hud = t
// redraw airspace when the AIRSPACE toggle changes
useEffect(() => { refreshAirspaceRef.current && refreshAirspaceRef.current(); }, [airspace]); // eslint-disable-line
// NEXRAD precip cells (green/yellow/red) — toggled by the MFD NEXRAD softkey.
useEffect(() => {
const layer = wxLayerRef.current; if (!layer) return;
layer.clearLayers();
if (!nexrad) return;
const col = { 1: '#1fa83a', 2: '#e6c200', 3: '#e23131' };
for (const c of (values.wxCells || [])) {
if (!isFinite(c.lat)) continue;
L.circle([c.lat, c.lon], { radius: (c.r || 5) * 1852, stroke: false, fillColor: col[c.lvl] || col[1], fillOpacity: 0.32, interactive: false }).addTo(layer);
}
}, [nexrad, values.wxCells]); // eslint-disable-line
// TCAS traffic targets — toggled by the MFD TRAFFIC softkey.
useEffect(() => {
const layer = trafficLayerRef.current; if (!layer) return;
layer.clearLayers();
if (!traffic) return;
for (const tgt of (values.traffic || [])) { if (isFinite(tgt.lat)) layer.addLayer(tcasSymbol(tgt)); }
}, [traffic, values.traffic]); // eslint-disable-line
// TERRAIN AWARENESS overlay: colour the elevation grid (from the FlyWithLua
// terrain probe) relative to aircraft altitude — red within 100 ft below/above,
// yellow 1001000 ft below, transparent otherwise (G1000 TAWS colours). Only
+4
View File
@@ -411,6 +411,10 @@ body {
.nav-sym { position: relative; width: 18px; height: 18px; }
.nav-lbl { position: absolute; left: 19px; top: 1px; font: 700 11px/1 monospace; white-space: nowrap;
text-shadow: 0 0 2px #000, 0 0 2px #000, 1px 1px 1px #000; }
/* TCAS traffic target: diamond + relative-altitude label */
.tcas-sym { position: relative; width: 16px; height: 16px; }
.tcas-lbl { position: absolute; left: 50%; top: -11px; transform: translateX(-50%); font: 700 10px/1 monospace; white-space: nowrap;
text-shadow: 0 0 2px #000, 0 0 2px #000, 1px 1px 1px #000; }
/* Bank pivots about the aircraft reference (attitude centre), which sits ~28%
down the full-screen terrain box — so terrain roll tracks the attitude. */
.svt-canvas { width: 100%; height: 100%; transform-origin: 50% 28%; }