import React, { useEffect, useRef, useState } from 'react'; import { num, navSearch } from '../api/useXplane.js'; // G1000 Direct-To (D→) dialog. Type or pick a waypoint ident; ACTIVATE flies a // direct magenta leg from the present position to it. We model that by setting // the shared flight plan to [PPOS → target] (the map/HSI already draw the leg) // and also firing the in-sim "direct" command so the real G1000 follows along. const R_NM = 3440.065; const rad = (d) => (d * Math.PI) / 180; function distBrg(la1, lo1, la2, lo2) { const dLat = rad(la2 - la1), dLon = rad(lo2 - lo1); const a = Math.sin(dLat / 2) ** 2 + Math.cos(rad(la1)) * Math.cos(rad(la2)) * Math.sin(dLon / 2) ** 2; const dist = 2 * R_NM * Math.asin(Math.min(1, Math.sqrt(a))); const y = Math.sin(rad(lo2 - lo1)) * Math.cos(rad(la2)); const x = Math.cos(rad(la1)) * Math.sin(rad(la2)) - Math.sin(rad(la1)) * Math.cos(rad(la2)) * Math.cos(rad(lo2 - lo1)); const brg = (Math.atan2(y, x) * 180 / Math.PI + 360) % 360; return { dist, brg }; } export default function DirectTo({ xp, onClose }) { const { values, fp, command } = xp; const [entry, setEntry] = useState(''); const [hits, setHits] = useState([]); const [sel, setSel] = useState(null); // chosen { id, lat, lon, type } const inputRef = useRef(null); useEffect(() => { inputRef.current?.focus(); }, []); // Live ident search against X-Plane's nav database. useEffect(() => { const q = entry.trim(); if (q.length < 2 || /[,\s]/.test(q)) { setHits([]); return; } let alive = true; navSearch(q).then((r) => alive && setHits(r.slice(0, 6))); return () => { alive = false; }; }, [entry]); const lat = num(values.lat), lon = num(values.lon); const preview = sel && isFinite(lat) ? distBrg(lat, lon, sel.lat, sel.lon) : null; const activate = () => { if (!sel) return; fp.set({ name: 'ACTIVE', waypoints: [ { id: 'PPOS', lat, lon, type: 'USR' }, { id: sel.id, lat: sel.lat, lon: sel.lon, type: sel.type || 'WPT' }, ] }); command('direct'); // mirror to the in-sim G1000 onClose(); }; return (
e.stopPropagation()}>
DIRECT TO
{/* ident line (cyan, edited like the FMS knob) + resolved name below */} { setEntry(e.target.value.toUpperCase()); setSel(null); }} onKeyDown={(e) => { if (e.key === 'Enter' && sel) activate(); if (e.key === 'Escape') onClose(); }} placeholder="_ _ _ _" autoCapitalize="characters" autoCorrect="off" spellCheck="false" />
{sel ? (sel.name || sel.type) : ' '}
{hits.length > 0 && !sel && (
{hits.map((h) => ( ))}
)}
ALT_____FTOFFSET+0NM BRG{preview ? `${String(Math.round(preview.brg)).padStart(3, '0')}°` : '___°'} DIS{preview ? `${preview.dist.toFixed(1)}NM` : '__._NM'} CRS{preview ? `${String(Math.round(preview.brg)).padStart(3, '0')}°` : '___°'}
); }