@@ -2,7 +2,6 @@ import { create } from 'zustand';
22import { PersistenceService } from '../../../core/services/persistence-service' ;
33import { AppLogger } from '../../../core/utils/logger' ;
44import {
5- calculateDistanceNm ,
65 pickString ,
76 toJsonMap ,
87} from '../../../core/utils/parse-utils' ;
@@ -39,6 +38,13 @@ import {
3938 parseRunwayNavaid ,
4039 parseTaxiwayFile ,
4140} from '../services/map-response-parsers' ;
41+ import { evaluateFlightAlerts } from '../services/flight-alerts' ;
42+ import { resolveHudTimerAction } from '../services/hud-timer-rules' ;
43+ import {
44+ appendRoutePoint ,
45+ buildAirportsFromSnapshot ,
46+ distanceInMeters ,
47+ } from '../services/map-telemetry' ;
4248import { fetchAirportWeather } from '../services/airport-weather' ;
4349import { parseAirportDetail } from '../services/map-airport-parser' ;
4450
@@ -72,9 +78,7 @@ const DEFAULT_DESCENT_WARNING_FPM = -3000;
7278const DEFAULT_DESCENT_DANGER_FPM = - 5000 ;
7379
7480/** 航迹最多保留的点数,避免长航班无限增长 */
75- const MAX_ROUTE_POINTS = 4000 ;
7681/** 相邻航迹点最小间隔(米),低于此值不记点 */
77- const MIN_ROUTE_POINT_DISTANCE_M = 30 ;
7882/** 判定「移动中」的地速门槛(kt) */
7983const MOVING_GROUND_SPEED_KT = 1.5 ;
8084/** UI 刷新限流间隔(ms)—— 地图重绘开销大 */
@@ -495,8 +499,11 @@ export const useMapStore = create<MapState>((set, get) => ({
495499 patch . isAircraftMoving = isMoving ;
496500
497501 // 航迹累积:与上一点距离足够才记
498- const nextRoute = appendRoutePoint ( state . route , aircraft ) ;
499- if ( nextRoute !== state . route ) patch . route = nextRoute ;
502+ const appended = appendRoutePoint ( state . route , aircraft , ctx . lastRoutePoint ) ;
503+ if ( appended . appended ) {
504+ patch . route = appended . route ;
505+ ctx . lastRoutePoint = appended . lastPoint ;
506+ }
500507
501508 // 起降点标记:onGround 翻转时打点
502509 const onGround = flightData . onGround ;
@@ -507,7 +514,16 @@ export const useMapStore = create<MapState>((set, get) => ({
507514 }
508515 ctx . lastOnGround = onGround ;
509516
510- handleAutoHudTimer ( state , aircraft , isMoving , patch ) ;
517+ // 判定是纯函数,副作用留在这里
518+ const timerAction = resolveHudTimerAction ( state , aircraft , isMoving ) ;
519+ if ( timerAction === 'start' ) {
520+ useMapStore . getState ( ) . startHudTimer ( ) ;
521+ patch . hasHudTimerStarted = true ;
522+ patch . isHudTimerRunning = true ;
523+ } else if ( timerAction === 'stop' ) {
524+ useMapStore . getState ( ) . pauseHudTimer ( ) ;
525+ patch . isHudTimerRunning = false ;
526+ }
511527 } else if ( ! isConnected ) {
512528 patch . aircraft = null ;
513529 patch . aiAircraft = [ ] ;
@@ -1252,185 +1268,10 @@ function isLayerStyle(value: unknown): value is MapLayerStyle {
12521268}
12531269
12541270
1255- function distanceInMeters ( lat1 : number , lon1 : number , lat2 : number , lon2 : number ) : number {
1256- return calculateDistanceNm ( lat1 , lon1 , lat2 , lon2 ) * 1852 ;
1257- }
1258-
1259- /** 航迹追加:距上一点足够远才记,并裁剪到最大点数 */
1260- function appendRoutePoint (
1261- route : MapRoutePoint [ ] ,
1262- aircraft : MapAircraftState ,
1263- ) : MapRoutePoint [ ] {
1264- const last = ctx . lastRoutePoint ;
1265- if (
1266- last &&
1267- distanceInMeters (
1268- last . latitude ,
1269- last . longitude ,
1270- aircraft . position . latitude ,
1271- aircraft . position . longitude ,
1272- ) < MIN_ROUTE_POINT_DISTANCE_M
1273- ) {
1274- return route ;
1275- }
1276- ctx . lastRoutePoint = aircraft . position ;
1277- const next : MapRoutePoint [ ] = [
1278- ...route ,
1279- {
1280- latitude : aircraft . position . latitude ,
1281- longitude : aircraft . position . longitude ,
1282- altitude : aircraft . altitude ,
1283- groundSpeed : aircraft . groundSpeed ,
1284- timestamp : new Date ( ) ,
1285- } ,
1286- ] ;
1287- return next . length > MAX_ROUTE_POINTS ? next . slice ( next . length - MAX_ROUTE_POINTS ) : next ;
1288- }
1289-
1290- function buildAirportsFromSnapshot ( snapshot : FlightDataSnapshot ) : MapAirportMarker [ ] {
1291- const result : MapAirportMarker [ ] = [ ] ;
1292- const seen = new Set < string > ( ) ;
1293-
1294- const push = (
1295- airport : { icaoCode : string ; name : string ; latitude : number ; longitude : number } | undefined ,
1296- isPrimary : boolean ,
1297- ) => {
1298- if ( ! airport ) return ;
1299- const code = airport . icaoCode . trim ( ) . toUpperCase ( ) ;
1300- if ( code . length === 0 || seen . has ( code ) ) return ;
1301- if ( ! isValidCoordinate ( airport . latitude , airport . longitude ) ) return ;
1302- seen . add ( code ) ;
1303- result . push ( {
1304- code,
1305- name : airport . name ,
1306- position : { latitude : airport . latitude , longitude : airport . longitude } ,
1307- isPrimary,
1308- } ) ;
1309- } ;
1310-
1311- push ( snapshot . departureAirport , true ) ;
1312- push ( snapshot . destinationAirport , true ) ;
1313- push ( snapshot . alternateAirport , true ) ;
1314- push ( snapshot . nearestAirport , false ) ;
1315- for ( const airport of snapshot . suggestedAirports ) push ( airport , false ) ;
1316-
1317- return result ;
1318- }
1319-
1320- /**
1321- * 飞行告警评估
1322- * 与桌面版一致:失速、超速、爬升/下降率超限、大坡度、大迎角
1323- */
1324- function evaluateFlightAlerts ( state : MapState , flightData : FlightData ) : MapFlightAlert [ ] {
1325- if ( ! state . alertsEnabled || ! state . isConnected ) return [ ] ;
1326-
1327- const alerts : MapFlightAlert [ ] = [ ] ;
1328- const enabled = ( id : string ) => ! state . disabledAlertIds . includes ( id ) ;
1329-
1330- if ( enabled ( 'stall_warning' ) && flightData . stallWarning === true ) {
1331- alerts . push ( { id : 'stall_warning' , level : 'danger' , message : 'STALL' } ) ;
1332- }
1333-
1334- const verticalSpeed = flightData . verticalSpeed ;
1335- if ( verticalSpeed !== undefined ) {
1336- if ( enabled ( 'excessive_climb_rate' ) ) {
1337- if ( verticalSpeed >= state . climbRateDangerFpm ) {
1338- alerts . push ( { id : 'excessive_climb_rate' , level : 'danger' , message : 'CLIMB RATE' } ) ;
1339- } else if ( verticalSpeed >= state . climbRateWarningFpm ) {
1340- alerts . push ( { id : 'excessive_climb_rate' , level : 'warning' , message : 'CLIMB RATE' } ) ;
1341- }
1342- }
1343- if ( enabled ( 'excessive_descent_rate' ) ) {
1344- if ( verticalSpeed <= state . descentRateDangerFpm ) {
1345- alerts . push ( { id : 'excessive_descent_rate' , level : 'danger' , message : 'SINK RATE' } ) ;
1346- } else if ( verticalSpeed <= state . descentRateWarningFpm ) {
1347- alerts . push ( { id : 'excessive_descent_rate' , level : 'warning' , message : 'SINK RATE' } ) ;
1348- }
1349- }
1350- }
1351-
1352- const bank = flightData . bank ;
1353- if ( enabled ( 'bank_angle' ) && bank !== undefined && Math . abs ( bank ) >= 45 ) {
1354- alerts . push ( {
1355- id : 'bank_angle' ,
1356- level : Math . abs ( bank ) >= 60 ? 'danger' : 'warning' ,
1357- message : 'BANK ANGLE' ,
1358- } ) ;
1359- }
13601271
1361- const aoa = flightData . angleOfAttack ;
1362- if ( enabled ( 'high_aoa' ) && aoa !== undefined && aoa >= 15 ) {
1363- alerts . push ( { id : 'high_aoa' , level : aoa >= 18 ? 'danger' : 'caution' , message : 'HIGH AOA' } ) ;
1364- }
13651272
1366- // 近地告警:低无线电高度 + 大下降率
1367- const radioAltitude = flightData . radioAltitude ;
1368- if (
1369- enabled ( 'terrain_warning' ) &&
1370- state . showTerrainWarning &&
1371- radioAltitude !== undefined &&
1372- radioAltitude < 1000 &&
1373- verticalSpeed !== undefined &&
1374- verticalSpeed < - 1500 &&
1375- flightData . gearDown !== true
1376- ) {
1377- alerts . push ( { id : 'terrain_warning' , level : 'danger' , message : 'TERRAIN' } ) ;
1378- }
13791273
1380- return alerts ;
1381- }
13821274
1383- /**
1384- * HUD 计时器自动启停
1385- *
1386- * 起:按所选模式判定「开始滑行 / 推出 / 任意移动」
1387- * 停:按所选模式判定「稳定落地 / 脱离跑道 / 到达停机位」
1388- */
1389- function handleAutoHudTimer (
1390- state : MapState ,
1391- aircraft : MapAircraftState ,
1392- isMoving : boolean ,
1393- patch : Partial < MapState > ,
1394- ) : void {
1395- if ( ! state . autoHudTimerEnabled ) return ;
1396-
1397- const onGround = aircraft . onGround ?? true ;
1398- const groundSpeed = aircraft . groundSpeed ?? 0 ;
1399-
1400- // ── 自动启动 ──
1401- if ( ! state . hasHudTimerStarted && isMoving ) {
1402- const shouldStart =
1403- state . autoTimerStartMode === 'anyMovement'
1404- ? true
1405- : state . autoTimerStartMode === 'pushback'
1406- ? onGround && aircraft . parkingBrake !== true
1407- : // runwayMovement:地速超过 30kt 视为进跑道加速
1408- onGround && groundSpeed >= 30 ;
1409-
1410- if ( shouldStart ) {
1411- useMapStore . getState ( ) . startHudTimer ( ) ;
1412- patch . hasHudTimerStarted = true ;
1413- patch . isHudTimerRunning = true ;
1414- }
1415- return ;
1416- }
1417-
1418- // ── 自动停止 ──
1419- if ( state . isHudTimerRunning && onGround ) {
1420- const shouldStop =
1421- state . autoTimerStopMode === 'parkingArrival'
1422- ? groundSpeed < 1 && aircraft . parkingBrake === true
1423- : state . autoTimerStopMode === 'runwayExitAfterLanding'
1424- ? groundSpeed < 30
1425- : // stableLanding:完全停稳
1426- groundSpeed < 5 ;
1427-
1428- if ( shouldStop ) {
1429- useMapStore . getState ( ) . pauseHudTimer ( ) ;
1430- patch . isHudTimerRunning = false ;
1431- }
1432- }
1433- }
14341275
14351276/**
14361277 * 上报新出现的地形告警
0 commit comments