Files
dmc/app/lib/domain/waypoint/flat_waypoint_list.dart
Constantin LeueandClaude Sonnet 5 1b13c45cac Add wind analysis: header pill, fetch/validate menu, speed-plot overlay
Portiert das Wind-System des HTML-Demonstrators (Doku 3.8) vollstaendig:
Open-Meteo-Winddaten, interpoliert zwischen Druckflaechen- und festen
Nabenhoehen-Stuetzpunkten auf die tatsaechliche Zielhoehe.

Domain (lib/domain/wind/wind_math.dart, reines Dart): Hoehen-
Interpolation (linear + zirkulaer fuer Windrichtung), Peilung zwischen
zwei Punkten, Kopf-/Rueckenwind-Komponente pro Flugleg - 1:1 uebernommen
aus interpolateWindAtHeight()/bearingBetween()/headTailwindComponent()
des Demonstrators, per Unit-Tests abgesichert.

Service (lib/services/wind/wind_service.dart): zwei getrennte,
fokussierte Open-Meteo-Requests statt eines kombinierten (Doku 4.10 -
ein 35-Variablen-Request verursachte im Demonstrator Modellauswahl-
Fehler). Drei Verbraucher: Kopfleisten-Pille (120 m AGL ueber
Referenzort), gebuendelter Wegpunkt-Fetch (ein Request fuer alle
Koordinaten via Open-Meteos Mehrfach-Location-Syntax) und das
Validierungspanel (rohe Messwerte je Nabenhoehe/Druckflaeche). Per
MockClient getestet (kein echter Netzwerkzugriff in Tests).

UI:
- HeaderWindPill (TopModeBar, nur ausserhalb Fly-Modus): zeigt
  Windrichtung/-geschwindigkeit, Antippen schaltet perspektivisch die
  Wind-Marker der Wegpunkte um (Zustand vorbereitet, HTML-Demonstrator:
  showPerWaypointWind).
- WaypointListPanel: "Fetch wind"/"Validate"-Buttons im Panel-Header,
  neue WIND-Spalte in der Liste.
- WindValidatePanel: neue Vollbild-Route (gleiches Muster wie
  WaypointListPanel) mit Rohdaten je Hoehenstufe inkl. Boeen (Doku 4.9:
  Boeen nur hier, nicht in der Wegpunktanzeige) und Interpolationsergebnis.
- FullValueChart (Speed-Tab): Kopf-/Rueckenwind-Diamanten pro Leg samt
  gestrichelter Nulllinie sowie gestrichelte Bodengeschwindigkeits-
  Stufenlinie.

Bug gefunden und gefixt waehrend der Emulator-Verifikation: die
Y-Achsen-Autoskalierung klemmte weiterhin auf den gueltigen Drehrad-
Wertebereich (13-25 m/s), wodurch nahe Null liegende Windkomponenten
(bei schwachem Wind der Normalfall) ausserhalb des sichtbaren Bereichs
lagen und weder Diamanten noch Nulllinie zu sehen waren. Der HTML-
Demonstrator klemmt computeSpeedChartRange() bewusst nicht auf SPD_MIN/
SPD_MAX; das Klemmen in FullValueChart._range() jetzt entsprechend
entfernt.

Waypoint-Modell um windSpeedMs/windDirFromDeg/windElevationM erweitert
(nullable, reine Planungshilfe - der Encoder liest diese Felder nie,
Doku 3.2). currentMissionProvider bekommt replaceAll() fuer den
gebuendelten Wind-Fetch, der alle Wegpunkte gleichzeitig aktualisiert.

Verifiziert: flutter analyze (0 issues), flutter test (44/44, davon 14
neue Wind-Mathematik- und 6 neue WindService-Tests), manuell auf
Pixel_10a-Emulator mit echtem Open-Meteo-Netzwerkzugriff - Kopfleisten-
Pille zeigt Live-Wind, Fetch/Validate fuellen Liste bzw. oeffnen das
Validierungspanel mit echten Messwerten, Speed-Plot zeigt nach dem Fix
Diamanten/Nulllinie/Bodengeschwindigkeit korrekt skaliert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-28 13:32:15 +02:00

84 lines
2.6 KiB
Dart

/// Protokollneutrales Wegpunkt-Modell (Architektur-Doku 3.2).
/// Divergenz zu MSP/MAVLink findet ausschliesslich im Encoder-Schritt statt
/// (MspWaypointEncoder / MavlinkMissionEncoder), nicht in diesem Modell.
library;
enum WaypointAction { none, loiter, landing, jump }
class Waypoint {
const Waypoint({
required this.lat,
required this.lon,
required this.altitudeM,
required this.speedMs,
required this.catchRadiusM,
this.action = WaypointAction.none,
this.jumpTargetIndex,
this.jumpRepeatCount,
this.windSpeedMs,
this.windDirFromDeg,
this.windElevationM,
});
final double lat;
final double lon;
final double altitudeM;
final double speedMs;
final double catchRadiusM;
final WaypointAction action;
/// Nur bei action == jump gesetzt (iNAV Action-Code 6, Doku 7.1).
final int? jumpTargetIndex;
/// Nur bei action == jump gesetzt; null bedeutet Endlos-Wiederholung.
final int? jumpRepeatCount;
/// Auf Hoehe altitudeM interpolierte Windgeschwindigkeit (Doku 3.8), null
/// bis "Fetch wind" ausgefuehrt wurde. Reine Planungshilfe, nicht Teil des
/// protokollneutralen Missionsinhalts - der Encoder liest diese Felder nie.
final double? windSpeedMs;
/// Windrichtung, aus der der Wind weht (Grad, 0=Nord), analog windSpeedMs.
final double? windDirFromDeg;
/// Bodenhoehe (ASL) am Wegpunkt, wie von der Wind-API geliefert - noetig,
/// um windSpeedMs/windDirFromDeg spaeter neu zu interpolieren oder
/// anzuzeigen (z.B. "interpoliert auf X m AMSL").
final double? windElevationM;
Waypoint copyWith({
double? lat,
double? lon,
double? altitudeM,
double? speedMs,
double? catchRadiusM,
WaypointAction? action,
int? jumpTargetIndex,
int? jumpRepeatCount,
double? windSpeedMs,
double? windDirFromDeg,
double? windElevationM,
}) {
return Waypoint(
lat: lat ?? this.lat,
lon: lon ?? this.lon,
altitudeM: altitudeM ?? this.altitudeM,
speedMs: speedMs ?? this.speedMs,
catchRadiusM: catchRadiusM ?? this.catchRadiusM,
action: action ?? this.action,
jumpTargetIndex: jumpTargetIndex ?? this.jumpTargetIndex,
jumpRepeatCount: jumpRepeatCount ?? this.jumpRepeatCount,
windSpeedMs: windSpeedMs ?? this.windSpeedMs,
windDirFromDeg: windDirFromDeg ?? this.windDirFromDeg,
windElevationM: windElevationM ?? this.windElevationM,
);
}
}
/// Gemeinsame, protokollunabhaengige Datenstruktur aller Missionstypen.
class FlatWaypointList {
const FlatWaypointList(this.waypoints);
final List<Waypoint> waypoints;
}