G1000: VNAV descent profile + designated-altitude flight plan colouring
- CURRENT VNV PROFILE panel on the MFD flight-plan page: active VNV waypoint + target altitude, VS TGT (−3° path), VS REQ, V DEV, FPA, TIME TO TOD (manual S.64 / S.107) - enriched the VNAV computation (vsTgt / vDev / FPA / time-to-TOD) shared by the PFD VnavBox - flight-plan ALT column now shows designated (VNAV) altitudes in blue (S.105) - new Audio Panel + earlier manual-alignment batch already in this branch Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -64,6 +64,29 @@ export default function FplPage({ xp, full = false, onClose }) {
|
||||
const gs = num(values.groundspeed) * 1.94384;
|
||||
const ete = gs > 30 ? total / gs : null;
|
||||
|
||||
// CURRENT VNV PROFILE: descent to the next waypoint with a lower target
|
||||
// altitude (manual S.64/107). VS TGT for a -3° path, VS REQ to make it, V DEV
|
||||
// from the path, time-to-top-of-descent.
|
||||
const alt = num(values.altitude);
|
||||
let vnav = null;
|
||||
if (gs > 40) {
|
||||
let c = 0, pl = num(values.lat), po = num(values.lon);
|
||||
for (let i = Math.max(1, active); i < wps.length; i++) {
|
||||
c += distNm({ lat: pl, lon: po }, wps[i]); pl = wps[i].lat; po = wps[i].lon;
|
||||
const t = num(wps[i].alt);
|
||||
if (t > 0 && t < alt - 50) {
|
||||
const tan = Math.tan((3 * Math.PI) / 180);
|
||||
const vsTgt = -gs * tan * 101.27;
|
||||
const vsReq = c > 0 ? (t - alt) / (c / gs * 60) : 0;
|
||||
const vDev = alt - (t + c * 6076.12 * tan);
|
||||
const todNm = c - (alt - t) / (6076.12 * tan);
|
||||
vnav = { wptId: wps[i].id, tgtAlt: t, vsTgt, vsReq, vDev, fpa: 3.0, todSec: todNm > 0 ? (todNm / gs) * 3600 : 0 };
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
const fmtSec = (s) => { const m = Math.floor(s / 60), ss = Math.round(s % 60); return `${m}:${String(ss).padStart(2, '0')}`; };
|
||||
|
||||
return (
|
||||
<div className={`fpl ${full ? 'full' : 'win'}`}>
|
||||
<div className="fpl-head">
|
||||
@@ -83,11 +106,24 @@ export default function FplPage({ xp, full = false, onClose }) {
|
||||
<span className="r-dtk">{dtk == null ? '___' : `${String(dtk).padStart(3, '0')}°`}</span>
|
||||
<span className="r-dis">{orig ? '—' : d.toFixed(1)}</span>
|
||||
<span className="r-cum">{orig ? '—' : cum.toFixed(0)}</span>
|
||||
<span className="r-alt">{w.alt ? `${w.alt}` : '_____'}</span>
|
||||
<span className={`r-alt ${w.alt ? 'dsgn' : ''}`}>{w.alt ? `${w.alt}FT` : '_____'}</span>
|
||||
<button className="r-del" onClick={(e) => { e.stopPropagation(); fp.remove(i); }}>✕</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{full && (
|
||||
<div className="fpl-vnav">
|
||||
<div className="fpl-vnav-h">CURRENT VNV PROFILE</div>
|
||||
{vnav ? (
|
||||
<div className="fpl-vnav-grid">
|
||||
<b>ACTIVE VNV WPT</b><span className="vwpt">{vnav.tgtAlt}<u>FT</u> at {vnav.wptId}</span>
|
||||
<b>VS TGT</b><span>{Math.round(vnav.vsTgt)}<u>FPM</u></span><b>FPA</b><span>{vnav.fpa.toFixed(1)}°</span>
|
||||
<b>VS REQ</b><span>{Math.round(vnav.vsReq)}<u>FPM</u></span><b>TIME TO TOD</b><span>{fmtSec(vnav.todSec)}</span>
|
||||
<b>V DEV</b><span>{vnav.vDev >= 0 ? '+' : ''}{Math.round(vnav.vDev)}<u>FT</u></span>
|
||||
</div>
|
||||
) : <div className="fpl-vnav-none">— no active VNAV profile —</div>}
|
||||
</div>
|
||||
)}
|
||||
<div className="fpl-entry">
|
||||
{hits.length > 0 && (
|
||||
<div className="fpl-hits">
|
||||
|
||||
@@ -58,6 +58,10 @@ function fmtEte(s) {
|
||||
}
|
||||
// VNAV: nearest downstream waypoint with a lower altitude constraint, and the
|
||||
// vertical speed required to meet it at the current groundspeed.
|
||||
// VNAV descent profile to the next waypoint that has a (lower) target altitude:
|
||||
// target VS for a -3° flight path, required VS to make the restriction, vertical
|
||||
// deviation from that path, and time-to-top-of-descent. (Manual S.64 / S.107.)
|
||||
const VNAV_FPA = 3.0; // default flight-path angle (degrees)
|
||||
function vnavInfo(V, fp) {
|
||||
const wps = fp?.waypoints || [];
|
||||
const ai = Math.max(1, Math.min(wps.length - 1, fp?.activeLeg ?? 1));
|
||||
@@ -71,9 +75,16 @@ function vnavInfo(V, fp) {
|
||||
prevLat = wps[i].lat; prevLon = wps[i].lon;
|
||||
const tgt = num(wps[i].alt);
|
||||
if (tgt > 0 && tgt < alt - 50) {
|
||||
const tan = Math.tan((VNAV_FPA * Math.PI) / 180);
|
||||
const tMin = (cum / gs) * 60;
|
||||
const vsReq = tMin > 0 ? (tgt - alt) / tMin : 0;
|
||||
return { wptId: wps[i].id, tgtAlt: tgt, dist: cum, vsReq };
|
||||
const vsReq = tMin > 0 ? (tgt - alt) / tMin : 0; // fpm to make the fix
|
||||
const vsTgt = -gs * tan * 101.27; // fpm for the FPA at this GS
|
||||
const desiredAltNow = tgt + cum * 6076.12 * tan; // path altitude at present position
|
||||
const vDev = alt - desiredAltNow; // + = above path
|
||||
const descentNm = (alt - tgt) / (6076.12 * tan); // distance the descent itself takes
|
||||
const todNm = cum - descentNm; // distance ahead until TOD
|
||||
const todSec = todNm > 0 ? (todNm / gs) * 3600 : 0;
|
||||
return { wptId: wps[i].id, tgtAlt: tgt, dist: cum, vsReq, vsTgt, vDev, fpa: VNAV_FPA, todSec };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
+11
-1
@@ -247,7 +247,17 @@ body {
|
||||
.r-wpt { color: #0ff; font-weight: 700; } .r-wpt i { color: #0a8; font-style: normal; font-size: 10px; margin-left: 6px; }
|
||||
.r-wpt b { font-weight: 700; } .r-wpt b.cur { background: #19b8e6; color: #042230; padding: 0 4px; border-radius: 1px; }
|
||||
.r-dtk, .r-dis, .r-cum { color: #e7edf2; text-align: right; }
|
||||
.r-alt { color: #0ff; text-align: right; }
|
||||
.r-alt { color: #6f808d; text-align: right; }
|
||||
.r-alt.dsgn { color: #4fa8ff; } /* designated (VNAV) altitude = blue, per manual S.105 */
|
||||
/* CURRENT VNV PROFILE panel (MFD flight-plan page) */
|
||||
.fpl-vnav { border-top: 1px solid #2c343c; padding: 6px 12px 8px; font-family: 'Roboto Mono', monospace; }
|
||||
.fpl-vnav-h { color: #36d2ff; font-size: 11px; letter-spacing: 1px; margin-bottom: 5px; }
|
||||
.fpl-vnav-grid { display: grid; grid-template-columns: auto 1fr auto 1fr; gap: 3px 10px; align-items: baseline; }
|
||||
.fpl-vnav-grid b { color: #6f808d; font-weight: normal; font-size: 11px; }
|
||||
.fpl-vnav-grid span { color: #fff; font-size: 14px; }
|
||||
.fpl-vnav-grid span u { color: #6f808d; font-size: 9px; text-decoration: none; margin-left: 1px; }
|
||||
.fpl-vnav-grid .vwpt { color: #4fa8ff; }
|
||||
.fpl-vnav-none { color: #6f808d; font-size: 12px; }
|
||||
/* ORIG / DEST subtitle (PFD window) */
|
||||
.fpl-od { color: #36d2ff; text-align: center; font-family: 'Roboto Mono', monospace; font-size: 14px; padding: 3px 0; border-bottom: 1px solid #1c242c; letter-spacing: 1px; }
|
||||
/* compact window: DTK/DIS only (drop CUM/ALT), no editor — like the real FPL window */
|
||||
|
||||
Reference in New Issue
Block a user