====== Navigation und Wegpunkte ====== Eine Route ist eine Kette von Punkten auf einer Kugel. Was dazwischen zu rechnen ist: **Kurs**, **Strecke**, **wo bin ich relativ zur Linie** — und was der Wind daraus macht. Dieser Guide verbindet [[lyx_-_programmiersprache:units:geo:sphere|std.geo.sphere]] (Geometrie auf der Erde) mit [[lyx_-_programmiersprache:units:aero:perf|std.aero.perf]] (Wind und Kurven) und [[lyx_-_programmiersprache:units:aero:fplan|std.aero.fplan]] (Vorhersage über die Kette). → [[lyx_-_programmiersprache:guides|Guides]] · [[lyx_-_programmiersprache:guides:standardatmosphaere|Standardatmosphäre]] · [[lyx_-_programmiersprache:guides:mcdu|Flugführungsrechner bauen]] · [[lyx_-_programmiersprache:guides:kurs-im-raum|Kurs und Lage im 3D-Raum]] import std.geo.sphere; // Großkreis, Kurse, Zielpunkt, Abweichungen, Sichtverbindung import std.aero.perf; // Winddreieck, Kurvenvorhalt import std.aero.fplan; // Vorhersage entlang der Kette ---- ===== 1. Erst die richtige Unit wählen ===== Es gibt **zwei** Geo-Units, und die Wahl entscheidet über die Richtigkeit des Ergebnisses: ^ Unit ^ Rechnet ^ Wofür ^ | [[lyx_-_programmiersprache:units:geo|std.geo]] | ganzzahlige **Mikrograd**, flache Näherung | Rechtecke, Bounding-Boxen, Umkreissuche im Nahbereich | | [[lyx_-_programmiersprache:units:geo:sphere|std.geo.sphere]] | ''f64'' auf der **Kugel** | Navigation, Kurse, Strecken über Kontinente, Polnähe, Datumsgrenze | **Für Navigation immer ''std.geo.sphere''.** Die flachen Näherungen aus ''std.geo'' stimmen auf 95 km bis auf 0,6 %, auf 6000 km fehlen 6,5 %, auf der **Südhalbkugel 64 %**, und an der Datumsgrenze verrechnen sie sich um den Faktor 180 ([[https://github.com/SEOLizer/LyX-Compiler/issues/1888|#1888]]). Die Grenze von ''std.geo.sphere'' ist die Kugelform selbst: gegen das WGS84-Ellipsoid weicht sie um bis zu 0,5 % ab — rund 20 km auf einer Äquatorlänge, wenige Meter auf 100 km. Für Navigation, Funkstrecken und Reichweiten genügt das; für **Vermessung** nicht. Ein Punkt ist ''GeoPos'' mit ''lat'' und ''lon'' in **Grad** (nicht Bogenmaß, nicht Mikrograd): var eddh: GeoPos := GeoPosNew(53.6304, 9.9882); // Breite, Länge var ausGms: f64 := GeoFromDms(53.0, 37.0, 49.4, 1); // 53°37'49.4" N ---- ===== 2. Kurs und Strecke: Großkreis gegen Loxodrome ===== unit main; import std.geo.sphere; import std.io; import std.string; fn main(): int64 { var eddh: GeoPos := GeoPosNew(53.6304, 9.9882); // Hamburg var lepa: GeoPos := GeoPosNew(39.5517, 2.7388); // Palma PrintLn(StrConcat("Grosskreis: ", StrConcat(FloatToStr(GeoDistanceNm(eddh, lepa), 1), " NM"))); PrintLn(StrConcat("Anfangskurs: ", StrConcat(FloatToStr(GeoBearing(eddh, lepa), 1), " Grad"))); PrintLn(StrConcat("Endkurs: ", StrConcat(FloatToStr(GeoFinalBearing(eddh, lepa), 1), " Grad"))); PrintLn(StrConcat("Loxodromkurs: ", StrConcat(FloatToStr(GeoRhumbBearing(eddh, lepa), 1), " Grad"))); PrintLn(StrConcat("Loxodrom: ", StrConcat(FloatToStr(GeoRhumbDistance(eddh, lepa) / 1852.0, 1), " NM"))); var m: GeoPos := GeoMidpoint(eddh, lepa); PrintLn(StrConcat("Mitte: ", StrConcat(FloatToStr(m.lat, 3), StrConcat(" / ", FloatToStr(m.lon, 3))))); return 0; } Grosskreis: 895.5 NM Anfangskurs: 202.2 Grad Endkurs: 196.9 Grad Loxodromkurs: 199.3 Grad Loxodrom: 895.8 NM Mitte: 46.647 / 5.890 Drei Dinge stehen in diesen Zahlen: * **Der Kurs ändert sich unterwegs.** Anfangskurs 202°, Endkurs 197° — ein Großkreis schneidet die Meridiane unter wechselndem Winkel. Wer einen Kurs für die ganze Strecke braucht, nimmt den **Loxodromkurs** (199°): eine Linie konstanten Kurses, dafür länger. * **Der Unterschied ist hier winzig** (0,3 NM auf 895), weil die Strecke überwiegend nach Süden führt. Auf einer Ost-West-Strecke in hohen Breiten sind es Prozent — deshalb fliegen Nordatlantikrouten Großkreise und keine geraden Linien auf der Karte. * ''GeoDistanceNm'' liefert **Seemeilen**, ''GeoDistance'' Meter, ''GeoDistanceKm'' Kilometer. Die Einheit steht im Namen; ''GeoRhumbDistance'' liefert Meter und wird oben von Hand auf NM gebracht. ---- ===== 3. Eine Route abarbeiten ===== Der eigentliche Kern: für jeden Abschnitt Kurs und Strecke aus der Geometrie, Grundgeschwindigkeit und Vorhaltewinkel aus dem Wind, Zeit und Treibstoff in die Vorhersage. unit main; import std.geo.sphere; import std.aero.perf; import std.aero.fplan; import std.io; import std.string; fn abschnitt(name: pchar, von: GeoPos, nach: GeoPos, tasKt: f64, windVon: f64, windKt: f64, ref v: AvVorhersage): void { var kurs: f64 := GeoBearing(von, nach); var strecke: f64 := GeoDistanceNm(von, nach); var gs: f64 := AvGrundgeschwindigkeitKt(kurs, tasKt, windVon, windKt); var wca: f64 := AvVorhaltewinkelGrad(kurs, tasKt, windVon, windKt); AvVorhersageAbschnitt(v, strecke, gs, 2400.0); var s: pchar := StrConcat(name, " Kurs="); s := StrConcat(s, FloatToStr(kurs, 0)); s := StrConcat(s, " d="); s := StrConcat(s, FloatToStr(strecke, 0)); s := StrConcat(s, " NM GS="); s := StrConcat(s, FloatToStr(gs, 0)); s := StrConcat(s, " WCA="); s := StrConcat(s, FloatToStr(wca, 1)); s := StrConcat(s, " ETA="); s := StrConcat(s, FloatToStr(AvVorhersageZeitMin(v), 0)); s := StrConcat(s, " min EFOB="); PrintLn(StrConcat(s, StrConcat(FloatToStr(AvVorhersageEfobKg(v), 0), " kg"))); } fn main(): int64 { var eddh: GeoPos := GeoPosNew(53.6304, 9.9882); var frd: GeoPos := GeoPosNew(50.0500, 8.5000); var dij: GeoPos := GeoPosNew(47.2690, 5.0900); var lepa: GeoPos := GeoPosNew(39.5517, 2.7388); var v: AvVorhersage := AvVorhersageStart(9000.0); abschnitt("EDDH->FRD ", eddh, frd, 440.0, 270.0, 40.0, v); abschnitt("FRD ->DIJ ", frd, dij, 440.0, 300.0, 60.0, v); abschnitt("DIJ ->LEPA", dij, lepa, 440.0, 340.0, 30.0, v); return 0; } EDDH->FRD Kurs=195 d=222 NM GS=428 WCA=5.0 ETA=31 min EFOB=7755 kg FRD ->DIJ Kurs=220 d=215 NM GS=425 WCA=7.7 ETA=61 min EFOB=6543 kg DIJ ->LEPA Kurs=193 d=475 NM GS=465 WCA=2.1 ETA=123 min EFOB=4093 kg **Die Grundgeschwindigkeit ist auf jedem Abschnitt eine andere** — 425 bis 465 kt bei gleichbleibenden 440 kt TAS, weil der Wind mal quer und mal von hinten kommt. Genau deshalb schreibt ''std.aero.fplan'' den Zustand je Abschnitt fort, statt eine Formel über die Gesamtstrecke anzubieten: eine Mittelung wäre bei Gegenwind auf der zweiten Hälfte gutmütig falsch. **Windrichtung ist die Richtung, aus der es weht** — meteorologische Konvention, wie in jeder Wettermeldung. ''AvWindkomponenteKt'' liefert positiv bei **Rückenwind**, ''AvSeitenwindKt'' immer den Betrag. ---- ===== 4. Wo bin ich relativ zur Linie? ===== Die zwei Zahlen, die auf jeder Navigationsanzeige stehen: **Querabweichung** (wie weit neben dem Kurs) und **Längsabweichung** (wie weit entlang der Strecke). unit main; import std.geo.sphere; import std.aero.perf; import std.io; import std.string; fn main(): int64 { var von: GeoPos := GeoPosNew(53.6304, 9.9882); var nach: GeoPos := GeoPosNew(50.0500, 8.5000); var ist: GeoPos := GeoPosNew(52.0000, 9.1000); // Ist-Position var xtk: f64 := GeoCrossTrack(ist, von, nach) / 1852.0; var atk: f64 := GeoAlongTrack(ist, von, nach) / 1852.0; var rest: f64 := GeoDistanceNm(von, nach) - atk; PrintLn(StrConcat("Querabweichung: ", StrConcat(FloatToStr(xtk, 2), " NM"))); PrintLn(StrConcat("zurueckgelegt: ", StrConcat(FloatToStr(atk, 1), " NM"))); PrintLn(StrConcat("Rest bis WPT: ", StrConcat(FloatToStr(rest, 1), " NM"))); PrintLn(StrConcat("Direktkurs: ", StrConcat(FloatToStr(GeoBearing(ist, nach), 1), " Grad"))); var neu: GeoPos := GeoDestination(von, 195.0, 25.0 * 1852.0); // 25 NM auf 195° PrintLn(StrConcat("neuer WPT: ", StrConcat(FloatToStr(neu.lat, 4), StrConcat(" / ", FloatToStr(neu.lon, 4))))); PrintLn(StrConcat("Kursaenderung 40 Grad -> Vorhalt ", StrConcat(FloatToStr(AvKurvenvorhaltNm(440.0, 25.0, 40.0), 2), " NM"))); PrintLn(StrConcat("Streckengewinn: ", StrConcat(FloatToStr(AvKurvenabkuerzungNm(440.0, 25.0, 40.0), 2), " NM"))); PrintLn(StrConcat("Kompasssektor: ", IntToStr(GeoCompassSector(GeoBearing(von, nach))))); return 0; } Querabweichung: 6.45 NM zurueckgelegt: 102.9 NM Rest bis WPT: 119.1 NM Direktkurs: 191.2 Grad neuer WPT: 53.2281 / 9.8082 Kursaenderung 40 Grad -> Vorhalt 2.20 NM Streckengewinn: 0.18 NM Kompasssektor: 9 **Das Vorzeichen der Querabweichung ist die halbe Aussage.** Positiv heißt **rechts** des Kurses, negativ links. Ohne Vorzeichen weiß man, wie weit man abgekommen ist, aber nicht, wohin man steuern muss. Dasselbe bei der Längsabweichung: **negativ heißt „noch vor dem Startpunkt"** — der Fußpunkt liegt hinter einem. Wer den Rest als ''Strecke − ATK'' rechnet, bekommt dann mehr als die Gesamtstrecke, und das ist richtig so. ''GeoDestination'' konstruiert einen Wegpunkt aus Kurs und Strecke — die Umkehrung von ''GeoBearing''/''GeoDistance''. Damit entstehen Zwischenpunkte, Ausweichpunkte und Warteschleifen-Fixes, die in keiner Datenbank stehen. **Achtung auf die Einheit:** die Strecke ist in **Metern** anzugeben. ''GeoCompassSector'' gibt die Richtung als Sektor 0…15 (0 = N, 4 = O, 8 = S, 12 = W) — hier 9 = SSW für Kurs 195°. ==== Kurvenvorhalt: warum die Route kürzer ist als die Summe der Abschnitte ==== Ein Flugzeug fliegt keine Ecken. Es beginnt die Kurve **vor** dem Wegpunkt und ist danach wieder auf der Linie — bei 440 kt und 25° Querneigung sind das 2,2 NM Vorhalt und 0,18 NM Streckengewinn je 40°-Knick. Über eine Route mit zwanzig Wegpunkten summiert sich das auf mehrere Meilen und eine knappe Minute. Ab **180° Kursänderung** liefert ''AvKurvenvorhaltNm'' den Fehlerwert: eine Kehrtwende lässt sich nicht abkürzen, und eine sehr große Zahl sähe aus wie ein Ergebnis. ---- ===== 5. Funkreichweite und Sicht ===== Ob ein Funkfeuer empfangen wird, ist eine Frage der Erdkrümmung — VOR und DME arbeiten auf Sichtlinie: unit main; import std.geo.sphere; import std.aero.atmos; import std.io; import std.string; fn main(): int64 { var vor: GeoPos := GeoPosNew(50.0500, 8.5000); // Funkfeuer auf 300 m var ac: GeoPos := GeoPosNew(47.2690, 5.0900); // Flugzeug in FL350 var hAc: f64 := AvFtToM(35000.0); PrintLn(StrConcat("Horizont Flugzeug: ", StrConcat(FloatToStr(GeoHorizon(hAc) / 1852.0, 0), " NM"))); PrintLn(StrConcat("Horizont Funkfeuer: ", StrConcat(FloatToStr(GeoHorizon(300.0) / 1852.0, 0), " NM"))); PrintLn(StrConcat("Entfernung: ", StrConcat(FloatToStr(GeoDistanceNm(vor, ac), 0), " NM"))); if (GeoLineOfSight(vor, 300.0, ac, hAc)) { PrintLn("Sichtverbindung: ja -- Empfang moeglich"); } else { PrintLn("Sichtverbindung: nein -- ausser Reichweite"); } return 0; } Horizont Flugzeug: 199 NM Horizont Funkfeuer: 33 NM Entfernung: 215 NM Sichtverbindung: ja -- Empfang moeglich Die Summe beider Horizonte (232 NM) übersteigt die Entfernung — also besteht Sicht. Das ist die geometrische Obergrenze; **Senderleistung, Antennencharakteristik und Geländeabschattung stehen hier nicht drin.** ''GeoLineOfSight'' beantwortet „kann es überhaupt gehen", nicht „geht es". ---- ===== 6. Was diese Units nicht können ===== ^ Fehlt ^ Folge für den Aufrufer ^ | **Missweisung** (magnetisch ↔ rechtweisend) | Alle Kurse sind **rechtweisend**. Wer magnetische Kurse braucht, bringt das Modell (WMM/IGRF) oder die Kartenangabe selbst mit | | **Ellipsoid** (WGS84, Vincenty) | Bis 0,5 % Abweichung. Für Vermessung ungeeignet | | **Höhe in der Streckenrechnung** | ''GeoDistance'' ist die Strecke auf der Kugeloberfläche, nicht die geflogene Schrägstrecke. Bei FL350 über 100 NM ist der Unterschied unter 0,1 % — bei kurzen Strecken mit großem Höhenunterschied nicht | | **Wegpunkt-Datenbank** | Namen, Kennungen, Luftstraßen und Verfahren kommen vom Aufrufer; die Units rechnen mit Koordinaten | | **Geländedaten** | Für Freigängigkeit und Abschattung braucht es ein Höhenmodell | ---- ===== 7. Merksätze ===== * **Navigation immer mit ''std.geo.sphere''** — ''std.geo'' ist für Rechtecke im Nahbereich, nicht für Kurse. * **Der Großkreiskurs ändert sich unterwegs.** Anfangs- und Endkurs sind zwei verschiedene Zahlen; wer einen festen Kurs braucht, nimmt die Loxodrome und die längere Strecke. * **Alle Kurse sind rechtweisend**, alle Strecken in ''GeoDistance*'' in der Einheit, die im Namen steht — ''GeoDestination'' und ''GeoCrossTrack'' arbeiten dagegen in **Metern**. * **Vorzeichen lesen**: Querabweichung positiv = rechts, Längsabweichung negativ = noch vor dem Start. * **Windrichtung ist die Richtung, aus der es weht** — Windkomponente positiv = Rückenwind. * Jede Funktion liefert bei unsinniger Eingabe **NaN** (''GeoIsError'', ''GeoPosIsError'') statt einer plausiblen Zahl. ---- Weiter: [[lyx_-_programmiersprache:guides:mcdu|Flugführungsrechner bauen]] setzt die Route in ein senkrechtes Profil um — Steig- und Sinkflug, T/C und T/D, Reserven. Für Lage und Drehungen im Raum (Quaternionen, Bezugsrahmen): [[lyx_-_programmiersprache:guides:kurs-im-raum|Kurs und Lage im 3D-Raum]]. Letzte Aktualisierung: 2026-09-07 — Beispiele und Fließtext auf die **englischen Namen** von ''std.geo.sphere'' gezogen (''GeoEntfernungSm'' → ''GeoDistanceNm'', ''GeoKurs'' → ''GeoBearing'', ''GeoQuerabweichung'' → ''GeoCrossTrack'' …); alle vier Vollprogramme neu übersetzt und ausgeführt. Vorherige letzte Aktualisierung: 2026-09-05 — Seite neu angelegt; alle Beispiele gegen lyxc 1.1.18A übersetzt und ausgeführt, die Ausgaben stammen aus dem Lauf. Prüflauf 2026-09-05 mit ''doku-pruefer.py'' über den gesamten Aero-Bereich (Repo-Compiler **lyxc 1.2.2A**): 13 Vollprogramme, 13 übersetzen, 0 echte Fehler; 193 Aufrufe gegen die ''pub fn''-Signaturen in ''aurum/std'' gehalten, 0 Abweichungen.