Auto-fetch wind, drop manual fetch/validate buttons, fix it in fly mode
Redesigns the waypoint list panel to match the drone-status/missions panels: no title bar, close button floats over the tab row, tabs get more vertical padding. Removes the "Fetch wind" and "Validate" header buttons along with the now-fully-orphaned WindValidatePanel screen and its WindService support code (fetchValidation, WindValidationResult and friends) - nothing else referenced them. Wind is now fetched automatically instead: HeaderWindNotifier gains ensurePerWaypointWind(), called when the waypoint list panel opens and when the header pill's per-waypoint toggle switches on. A lat/lon signature of the waypoint list tracks whether the fetched data is still current, so a ref.listen(currentMissionProvider) in the same notifier refetches only when the list itself changes (position added/ removed/moved) - not on every altitude/speed edit. Waypoints that already carry wind data (e.g. loaded from a saved mission) short- circuit the check instead of re-fetching pointlessly. Also wires per-waypoint wind markers into the fly-mode map, which never had them (plan_screen.dart built windMarkers; fly_screen.dart just never did) - the reported "doesn't work in fly mode" bug. windMarkerWidth() moved from a plan_screen.dart-private method to wind_marker_pill.dart so both screens can share it. Test containers in widget_test.dart now override windServiceProvider with a MockClient so the new auto-fetch trigger can't reach the real network during tests.
This commit is contained in:
@@ -15,64 +15,6 @@ class WindCombinedLevels {
|
||||
final double elevationM;
|
||||
}
|
||||
|
||||
class WindValidationFixedReading {
|
||||
const WindValidationFixedReading({
|
||||
required this.heightAglM,
|
||||
this.speedMs,
|
||||
this.dirFromDeg,
|
||||
});
|
||||
|
||||
final int heightAglM;
|
||||
final double? speedMs;
|
||||
final double? dirFromDeg;
|
||||
}
|
||||
|
||||
class WindValidationPressureReading {
|
||||
const WindValidationPressureReading({
|
||||
required this.pressureHpa,
|
||||
this.heightAmslM,
|
||||
this.speedMs,
|
||||
this.dirFromDeg,
|
||||
required this.excluded,
|
||||
});
|
||||
|
||||
final int pressureHpa;
|
||||
final double? heightAmslM;
|
||||
final double? speedMs;
|
||||
final double? dirFromDeg;
|
||||
|
||||
/// True, wenn dieser Stuetzpunkt unvollstaendig ist oder unterhalb der
|
||||
/// Bodenhoehe liegt und daher von der Interpolation ausgeschlossen wurde.
|
||||
final bool excluded;
|
||||
}
|
||||
|
||||
/// Ergebnis des Wind-Validierungspanels (Doku 3.8): rohe Messwerte auf
|
||||
/// festen Nabenhoehen und Druckflaechen, damit nachvollziehbar bleibt, wie
|
||||
/// der interpolierte Wert an [interpolatedAt120] zustande kam.
|
||||
class WindValidationResult {
|
||||
const WindValidationResult({
|
||||
required this.lat,
|
||||
required this.lon,
|
||||
required this.elevationM,
|
||||
required this.fixedRequestFailed,
|
||||
required this.fixedReadings,
|
||||
this.gustMs,
|
||||
required this.pressureRequestFailed,
|
||||
required this.pressureReadings,
|
||||
this.interpolatedAt120,
|
||||
});
|
||||
|
||||
final double lat;
|
||||
final double lon;
|
||||
final double elevationM;
|
||||
final bool fixedRequestFailed;
|
||||
final List<WindValidationFixedReading> fixedReadings;
|
||||
final double? gustMs;
|
||||
final bool pressureRequestFailed;
|
||||
final List<WindValidationPressureReading> pressureReadings;
|
||||
final WindSample? interpolatedAt120;
|
||||
}
|
||||
|
||||
/// Holt und kombiniert Winddaten von Open-Meteo (Doku 3.8). Zwei getrennte,
|
||||
/// fokussierte Requests (Druckflaechen + feste Nabenhoehen) statt eines
|
||||
/// kombinierten - ein 35-Variablen-Request verursachte im HTML-Demonstrator
|
||||
@@ -261,98 +203,4 @@ class WindService {
|
||||
windElevationM: combined.elevationM,
|
||||
);
|
||||
}
|
||||
|
||||
/// Detaillierte Rohdaten fuer das Wind-Validierungspanel: einzelne
|
||||
/// Messwerte je Nabenhoehe/Druckflaeche, damit nachvollziehbar bleibt, wie
|
||||
/// der interpolierte 120-m-Wert zustande kam (Doku 3.8/4.9: Boeen nur
|
||||
/// hier, nicht in der Wegpunktanzeige).
|
||||
Future<WindValidationResult> fetchValidation(double lat, double lon) async {
|
||||
final lats = lat.toStringAsFixed(5);
|
||||
final lons = lon.toStringAsFixed(5);
|
||||
final results = await Future.wait([
|
||||
_fetchJson(_fixedUri(lats, lons)),
|
||||
_fetchJson(_pressureUri(lats, lons)),
|
||||
]);
|
||||
final fixedRes = _asResultList(results[0], 1)[0];
|
||||
final pressureRes = _asResultList(results[1], 1)[0];
|
||||
|
||||
final elevation = ((fixedRes?['elevation'] as num?) ??
|
||||
(pressureRes?['elevation'] as num?))
|
||||
?.toDouble() ??
|
||||
0.0;
|
||||
|
||||
var fixedReadings = <WindValidationFixedReading>[];
|
||||
double? gust;
|
||||
final fHourly = fixedRes?['hourly'] as Map<String, dynamic>?;
|
||||
final fTime = fHourly?['time'] as List<dynamic>?;
|
||||
final fixedFailed = fHourly == null || fTime == null || fTime.isEmpty;
|
||||
if (!fixedFailed) {
|
||||
final idx = _nearestHourIndex(fTime);
|
||||
fixedReadings = [
|
||||
for (final h in _fixedHeights)
|
||||
WindValidationFixedReading(
|
||||
heightAglM: h,
|
||||
speedMs: ((fHourly['wind_speed_${h}m'] as List?)?[idx] as num?)
|
||||
?.toDouble(),
|
||||
dirFromDeg: ((fHourly['wind_direction_${h}m'] as List?)?[idx] as num?)
|
||||
?.toDouble(),
|
||||
),
|
||||
];
|
||||
gust = ((fHourly['wind_gusts_10m'] as List?)?[idx] as num?)?.toDouble();
|
||||
}
|
||||
|
||||
var pressureReadings = <WindValidationPressureReading>[];
|
||||
final pHourly = pressureRes?['hourly'] as Map<String, dynamic>?;
|
||||
final pTime = pHourly?['time'] as List<dynamic>?;
|
||||
final pressureFailed = pHourly == null || pTime == null || pTime.isEmpty;
|
||||
if (!pressureFailed) {
|
||||
final idx = _nearestHourIndex(pTime);
|
||||
pressureReadings = [
|
||||
for (final p in _pressureLevels)
|
||||
_pressureReadingAt(pHourly, idx, p, elevation),
|
||||
];
|
||||
}
|
||||
|
||||
final combined = buildCombinedWindLevels(pressureRes, fixedRes);
|
||||
final interpolated = combined.levels.isEmpty
|
||||
? null
|
||||
: interpolateWindAtHeight(combined.levels, elevation + 120);
|
||||
|
||||
return WindValidationResult(
|
||||
lat: lat,
|
||||
lon: lon,
|
||||
elevationM: elevation,
|
||||
fixedRequestFailed: fixedFailed,
|
||||
fixedReadings: fixedReadings,
|
||||
gustMs: gust,
|
||||
pressureRequestFailed: pressureFailed,
|
||||
pressureReadings: pressureReadings,
|
||||
interpolatedAt120: interpolated,
|
||||
);
|
||||
}
|
||||
|
||||
WindValidationPressureReading _pressureReadingAt(
|
||||
Map<String, dynamic> hourly,
|
||||
int idx,
|
||||
int pressureHpa,
|
||||
double elevationM,
|
||||
) {
|
||||
final h = (hourly['geopotential_height_${pressureHpa}hPa'] as List?)?[idx];
|
||||
final s = (hourly['wind_speed_${pressureHpa}hPa'] as List?)?[idx];
|
||||
final d = (hourly['wind_direction_${pressureHpa}hPa'] as List?)?[idx];
|
||||
final heightAmslM = h is num ? h.toDouble() : null;
|
||||
final speedMs = s is num ? s.toDouble() : null;
|
||||
final dirFromDeg = d is num ? d.toDouble() : null;
|
||||
final excluded = heightAmslM == null ||
|
||||
speedMs == null ||
|
||||
dirFromDeg == null ||
|
||||
heightAmslM < elevationM;
|
||||
return WindValidationPressureReading(
|
||||
pressureHpa: pressureHpa,
|
||||
heightAmslM: heightAmslM,
|
||||
speedMs: speedMs,
|
||||
dirFromDeg: dirFromDeg,
|
||||
excluded: excluded,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user