import 'package:flutter_test/flutter_test.dart'; import 'package:dmc_app/domain/mission/terrain_math.dart'; import 'package:dmc_app/domain/waypoint/flat_waypoint_list.dart'; void main() { group('routeKeyForTerrain', () { test('aendert sich, wenn sich ein Wegpunkt verschiebt', () { const a = [ Waypoint(lat: 52.0, lon: 4.0, altitudeM: 60, speedMs: 15, catchRadiusM: 60), ]; const b = [ Waypoint(lat: 52.001, lon: 4.0, altitudeM: 60, speedMs: 15, catchRadiusM: 60), ]; expect(routeKeyForTerrain(a), isNot(routeKeyForTerrain(b))); }); test('bleibt gleich bei reinen Wertaenderungen (Hoehe/Geschwindigkeit)', () { const a = [ Waypoint(lat: 52.0, lon: 4.0, altitudeM: 60, speedMs: 15, catchRadiusM: 60), ]; const b = [ Waypoint(lat: 52.0, lon: 4.0, altitudeM: 120, speedMs: 20, catchRadiusM: 60), ]; expect(routeKeyForTerrain(a), routeKeyForTerrain(b)); }); }); group('sampleCountFor', () { test('klemmt sehr kurze Routen auf mindestens 10 Samples', () { const waypoints = [ Waypoint(lat: 0, lon: 0, altitudeM: 60, speedMs: 15, catchRadiusM: 60), Waypoint(lat: 0.0001, lon: 0, altitudeM: 60, speedMs: 15, catchRadiusM: 60), ]; expect(sampleCountFor(waypoints), 10); }); test('folgt der 30-m-Sampling-Distanz fuer laengere Routen', () { // 1 Grad Breite ~ 111.320 km -> 0.01 Grad ~ 1113 m. const waypoints = [ Waypoint(lat: 0, lon: 0, altitudeM: 60, speedMs: 15, catchRadiusM: 60), Waypoint(lat: 0.01, lon: 0, altitudeM: 60, speedMs: 15, catchRadiusM: 60), ]; final count = sampleCountFor(waypoints); expect(count, greaterThan(10)); expect(count, closeTo(1113 / terrainSampleSpacingM, 2)); }); test('liefert 0 fuer eine einzelne Wegpunkt-Route', () { const waypoints = [ Waypoint(lat: 0, lon: 0, altitudeM: 60, speedMs: 15, catchRadiusM: 60), ]; expect(sampleCountFor(waypoints), 0); }); }); group('sampleRouteByDistance', () { const waypoints = [ Waypoint(lat: 0, lon: 0, altitudeM: 60, speedMs: 15, catchRadiusM: 60), Waypoint(lat: 0, lon: 0.01, altitudeM: 80, speedMs: 15, catchRadiusM: 60), ]; test('erstes und letztes Sample entsprechen den Wegpunkten', () { final samples = sampleRouteByDistance(waypoints, 10); expect(samples, hasLength(11)); expect(samples.first.distanceM, 0); expect(samples.first.point.latitude, closeTo(0, 1e-9)); expect(samples.first.point.longitude, closeTo(0, 1e-9)); expect(samples.last.point.longitude, closeTo(0.01, 1e-9)); }); test('Samples sind gleichmaessig nach Distanz verteilt', () { final samples = sampleRouteByDistance(waypoints, 4); final total = samples.last.distanceM; for (var i = 0; i < samples.length; i++) { expect(samples[i].distanceM, closeTo(total * i / 4, 1e-6)); } }); }); group('plannedAltitudeAt', () { const waypoints = [ Waypoint(lat: 0, lon: 0, altitudeM: 60, speedMs: 15, catchRadiusM: 60), Waypoint(lat: 0, lon: 0.01, altitudeM: 120, speedMs: 15, catchRadiusM: 60), ]; test('interpoliert linear zwischen zwei Wegpunkten', () { final total = sampleRouteByDistance(waypoints, 1).last.distanceM; expect(plannedAltitudeAt(waypoints, total / 2), closeTo(90, 0.5)); }); test('klemmt vor dem ersten und nach dem letzten Wegpunkt', () { expect(plannedAltitudeAt(waypoints, -100), 60); final total = sampleRouteByDistance(waypoints, 1).last.distanceM; expect(plannedAltitudeAt(waypoints, total + 1000), 120); }); }); group('terrainDangerRanges', () { const waypoints = [ Waypoint(lat: 0, lon: 0, altitudeM: 60, speedMs: 15, catchRadiusM: 60), Waypoint(lat: 0, lon: 0.01, altitudeM: 60, speedMs: 15, catchRadiusM: 60), ]; test('meldet keinen gefaehrlichen Bereich bei ausreichendem Abstand', () { final profile = TerrainProfile(routeKey: 'k', points: const [ TerrainSamplePoint(distanceM: 0, elevationRelM: 0), TerrainSamplePoint(distanceM: 500, elevationRelM: 0), TerrainSamplePoint(distanceM: 1000, elevationRelM: 0), ]); expect(terrainDangerRanges(waypoints, profile), isEmpty); }); test('markiert den Bereich, in dem der Bodenabstand unterschritten wird', () { // Geplante Hoehe konstant 60m; Clearance = 60 - elevRel. final profile = TerrainProfile(routeKey: 'k', points: const [ TerrainSamplePoint(distanceM: 0, elevationRelM: 0), // clearance 60 TerrainSamplePoint(distanceM: 300, elevationRelM: 55), // clearance 5 (< 10) TerrainSamplePoint(distanceM: 600, elevationRelM: 52), // clearance 8 (< 10) TerrainSamplePoint(distanceM: 900, elevationRelM: 0), // clearance 60 ]); final ranges = terrainDangerRanges(waypoints, profile); expect(ranges, [(300.0, 900.0)]); }); test('ignoriert Samples ohne geladene Hoehe', () { final profile = TerrainProfile(routeKey: 'k', points: const [ TerrainSamplePoint(distanceM: 0, elevationRelM: null), TerrainSamplePoint(distanceM: 500), ]); expect(terrainDangerRanges(waypoints, profile), isEmpty); }); test('schliesst einen bis zum letzten Sample andauernden Bereich ab', () { final profile = TerrainProfile(routeKey: 'k', points: const [ TerrainSamplePoint(distanceM: 0, elevationRelM: 55), // clearance 5 TerrainSamplePoint(distanceM: 500, elevationRelM: 55), ]); expect(terrainDangerRanges(waypoints, profile), [(0.0, 500.0)]); }); }); }