From c7a87b95e8d37307468deb8395bce32f887d13b8 Mon Sep 17 00:00:00 2001 From: Florian Gross <63071941+flogross89@users.noreply.github.com> Date: Mon, 8 Apr 2024 04:01:08 +0300 Subject: [PATCH] [OANS] draw stop line, update DRY/WET lines, visualize overrun status --- .../instruments/src/ND/OansControlPanel.tsx | 14 +- .../instruments/src/ND/oans-styles.scss | 29 +- .../src/OANC/BrakeToVacateUtils.ts | 261 ++++++++++++------ .../instruments/src/OANC/BtvPublisher.ts | 4 + .../src/systems/instruments/src/OANC/Oanc.tsx | 19 +- .../instruments/src/OANC/OancLabelFilter.ts | 2 +- .../src/OANC/OancPlanModeCompass.tsx | 2 +- .../systems/instruments/src/OANC/styles.scss | 29 +- 8 files changed, 254 insertions(+), 106 deletions(-) diff --git a/fbw-a380x/src/systems/instruments/src/ND/OansControlPanel.tsx b/fbw-a380x/src/systems/instruments/src/ND/OansControlPanel.tsx index 9da0d1cba4c..73d5dc6acbd 100644 --- a/fbw-a380x/src/systems/instruments/src/ND/OansControlPanel.tsx +++ b/fbw-a380x/src/systems/instruments/src/ND/OansControlPanel.tsx @@ -7,10 +7,9 @@ import { ArraySubject, ClockEvents, ComponentProps, DisplayComponent, EventBus, FSComponent, MapSubject, MappedSubject, MappedSubscribable, SimVarValueType, Subject, Subscribable, Subscription, VNode, } from '@microsoft/msfs-sdk'; -import { BrakeToVacateUtils, ControlPanelAirportSearchMode, ControlPanelStore, ControlPanelUtils, FmsDataStore, NavigraphAmdbClient, OansControlEvents, globalToAirportCoordinates } from '@flybywiresim/oanc'; +import { BrakeToVacateUtils, ControlPanelAirportSearchMode, ControlPanelStore, ControlPanelUtils, FmsDataStore, FmsOansDataArinc429, NavigraphAmdbClient, OansControlEvents, globalToAirportCoordinates } from '@flybywiresim/oanc'; import { AmdbAirportSearchResult, Arinc429RegisterSubject, EfisSide, FeatureType, FeatureTypeString, MathUtils, MsfsBackend, Runway } from '@flybywiresim/fbw-sdk'; -import { FmsOansData, FmsOansDataArinc429 } from 'instruments/src/MsfsAvionicsCommon/providers/FmsOansPublisher'; import { Button } from 'instruments/src/ND/UI/Button'; import { OansRunwayInfoBox } from 'instruments/src/ND/OANSRunwayInfoBox'; import { DropdownMenu } from './UI/DropdownMenu'; @@ -103,7 +102,7 @@ export class OansControlPanel extends DisplayComponent { private landingRunwayNavdata: Runway | undefined; - private readonly btvUtils = new BrakeToVacateUtils(this.props.bus); + private btvUtils = new BrakeToVacateUtils(this.props.bus); private readonly airportDatabase = Subject.create('SXT59027250AA04'); @@ -195,9 +194,12 @@ export class OansControlPanel extends DisplayComponent { if (destination && this.navigraphAvailable.get() === true) { const data = await this.amdbClient.getAirportData(destination, [FeatureTypeString.RunwayThreshold]); const thresholdFeature = data.runwaythreshold?.features.filter((td) => td.properties.feattype === FeatureType.RunwayThreshold && td.properties?.idthr === it.substring(2)); - if (thresholdFeature) { - this.runwayLda.set((thresholdFeature[0].properties.lda > 0 ? thresholdFeature[0].properties.lda : 0).toFixed(0)); - this.runwayTora.set((thresholdFeature[0].properties.tora > 0 ? thresholdFeature[0].properties.tora : 0).toFixed(0)); + if (thresholdFeature && thresholdFeature[0]?.properties.lda && thresholdFeature[0]?.properties.tora) { + this.runwayLda.set(((thresholdFeature[0].properties.lda) > 0 ? thresholdFeature[0].properties.lda : 0).toFixed(0)); + this.runwayTora.set((thresholdFeature[0].properties.tora > 0 ? thresholdFeature[0]?.properties.tora : 0).toFixed(0)); + } else { + this.runwayLda.set('N/A'); + this.runwayTora.set('N/A'); } } else if (destination && this.navigraphAvailable.get() === false) { const db = NavigationDatabaseService.activeDatabase.backendDatabase; diff --git a/fbw-a380x/src/systems/instruments/src/ND/oans-styles.scss b/fbw-a380x/src/systems/instruments/src/ND/oans-styles.scss index 36c83e00094..95efedfb4cb 100644 --- a/fbw-a380x/src/systems/instruments/src/ND/oans-styles.scss +++ b/fbw-a380x/src/systems/instruments/src/ND/oans-styles.scss @@ -151,14 +151,39 @@ color: $display-white; } -.oanc-label-style-btv-stop-line { +.oanc-label-style-btv-stop-line-green { + color: $display-green; +} + +.oanc-label-style-btv-stop-line-green:hover { + outline: 0px; +} + +.oanc-label-style-btv-stop-line-magenta { color: $display-magenta; } -.oanc-label-style-btv-stop-line:hover { +.oanc-label-style-btv-stop-line-magenta:hover { outline: 0px; } +.oanc-label-style-btv-stop-line-amber { + color: $display-amber; +} + +.oanc-label-style-btv-stop-line-amber:hover { + outline: 0px; +} + +.oanc-label-style-btv-stop-line-red { + color: $display-red; +} + +.oanc-label-style-btv-stop-line-red:hover { + outline: 0px; +} + + .oanc-button { padding: 10px 6px; background-color: gray; diff --git a/fbw-common/src/systems/instruments/src/OANC/BrakeToVacateUtils.ts b/fbw-common/src/systems/instruments/src/OANC/BrakeToVacateUtils.ts index 98ea670dcfb..8974ab8fa63 100644 --- a/fbw-common/src/systems/instruments/src/OANC/BrakeToVacateUtils.ts +++ b/fbw-common/src/systems/instruments/src/OANC/BrakeToVacateUtils.ts @@ -12,16 +12,23 @@ import { FmsOansData } from 'instruments/src/OANC/FmsOansPublisher'; import { OancLabelManager } from 'instruments/src/OANC/OancLabelManager'; import { fractionalPointAlongLine, pointDistance, pointToLineDistance } from 'instruments/src/OANC/OancMapUtils'; +const TOUCHDOWN_ZONE_DISTANCE = 400; + /** * Utility class for brake to vacate (BTV) functions on the A380 */ + export class BrakeToVacateUtils { constructor( private readonly bus: EventBus, - private labelManager: OancLabelManager, - private canvasRef: NodeReference, - private canvasCentreX: Subscribable, - private canvasCentreY: Subscribable, + private readonly labelManager?: OancLabelManager, + private readonly aircraftOnGround?: Subscribable, + private readonly aircraftPpos?: Position, + private readonly canvasRef?: NodeReference, + private readonly canvasCentreX?: Subscribable, + private readonly canvasCentreY?: Subscribable, + private readonly zoomLevelIndex?: Subscribable, + private getZoomLevelInverseScale?: () => number, ) { this.remaininingDistToExit.sub((v) => { if (v < 0) { @@ -42,19 +49,22 @@ export class BrakeToVacateUtils { const sub = this.bus.getSubscriber(); this.dryStoppingDistance.setConsumer(sub.on('dryStoppingDistance').whenChanged()); this.wetStoppingDistance.setConsumer(sub.on('wetStoppingDistance').whenChanged()); + this.liveStoppingDistance.setConsumer(sub.on('stopBarDistance').whenChanged()); this.dryStoppingDistance.sub(() => this.drawBtvLayer()); this.wetStoppingDistance.sub(() => this.drawBtvLayer()); + this.liveStoppingDistance.sub(() => this.drawBtvLayer()); + this.remaininingDistToRwyEnd.sub(() => this.drawBtvLayer()); + this.aircraftOnGround?.sub(() => this.drawBtvLayer()); + this.zoomLevelIndex?.sub(() => this.drawBtvLayer()); this.clearSelection(); } - private readonly dryStoppingDistance = ConsumerSubject.create(null, 0); - - private readonly wetStoppingDistance = ConsumerSubject.create(null, 0); - + /** Updated during deceleration on the ground */ private readonly remaininingDistToExit = Subject.create(0); + /** Updated during deceleration on the ground */ private readonly remaininingDistToRwyEnd = Subject.create(0); readonly btvRunway = Subject.create(null); @@ -82,13 +92,13 @@ export class BrakeToVacateUtils { private btvPathGeometry: Position[]; /** Stopping distance for dry rwy conditions, in meters. Null if not set. */ - readonly btvDryStoppingDistance = Subject.create(null); + private readonly dryStoppingDistance = ConsumerSubject.create(null, 0); /** Stopping distance for wet rwy conditions, in meters. Null if not set. */ - readonly btvWetStoppingDistance = Subject.create(null); + private readonly wetStoppingDistance = ConsumerSubject.create(null, 0); /** Live remaining stopping distance during deceleration, in meters. Null if not set. */ - readonly btvLiveStoppingDistance = Subject.create(null); + private readonly liveStoppingDistance = ConsumerSubject.create(null, 0); selectRunwayFromOans(runway: string, centerlineFeature: Feature, thresholdFeature: Feature) { this.clearSelection(); @@ -119,6 +129,8 @@ export class BrakeToVacateUtils { this.btvRunwayLda.set(lda); this.btvRunwayBearingTrue.set(heading); this.btvRunway.set(runway); + this.remaininingDistToRwyEnd.set(lda - TOUCHDOWN_ZONE_DISTANCE); + this.remaininingDistToExit.set(lda - TOUCHDOWN_ZONE_DISTANCE); const pub = this.bus.getPublisher(); pub.pub('oansSelectedLandingRunway', runway, true); @@ -137,13 +149,13 @@ export class BrakeToVacateUtils { if (exitDist1 < exitDist2) { // Check whether valid path: Exit start position (i.e. point of exit line closest to threshold) should be inside runway - if (pointToLineDistance(exitLoc1, this.btvThresholdPosition, this.btvOppositeThresholdPosition) > 20 || exitDist1 < 400) { + if (pointToLineDistance(exitLoc1, this.btvThresholdPosition, this.btvOppositeThresholdPosition) > 20 || exitDist1 < TOUCHDOWN_ZONE_DISTANCE) { return; } this.btvExitPosition = exitLoc1; } else { // Check whether valid path: Exit start position (i.e. point of exit line closest to threshold) should be inside runway - if (pointToLineDistance(exitLoc2, this.btvThresholdPosition, this.btvOppositeThresholdPosition) > 20 || exitDist2 < 400) { + if (pointToLineDistance(exitLoc2, this.btvThresholdPosition, this.btvOppositeThresholdPosition) > 20 || exitDist2 < TOUCHDOWN_ZONE_DISTANCE) { return; } this.btvExitPosition = exitLoc2; @@ -155,7 +167,7 @@ export class BrakeToVacateUtils { thrLoc[1], this.btvExitPosition[0], this.btvExitPosition[1], - ) - 400; + ) - TOUCHDOWN_ZONE_DISTANCE; this.bus.getPublisher().pub('oansSelectedExit', exit); Arinc429Word.toSimVarValue('L:A32NX_OANS_BTV_REQ_STOPPING_DISTANCE', exitDistance, Arinc429SignStatusMatrix.NormalOperation); @@ -183,6 +195,8 @@ export class BrakeToVacateUtils { this.btvRunwayLda.set(lda); this.btvRunwayBearingTrue.set(heading); this.btvRunway.set(runway); + this.remaininingDistToRwyEnd.set(lda - TOUCHDOWN_ZONE_DISTANCE); + this.remaininingDistToExit.set(lda - TOUCHDOWN_ZONE_DISTANCE); const pub = this.bus.getPublisher(); pub.pub('oansSelectedLandingRunway', runway, true); @@ -195,7 +209,7 @@ export class BrakeToVacateUtils { this.btvExitPosition = btvExitPosition; // Account for touchdown zone distance - const correctedStoppingDistance = reqStoppingDistance - 400; + const correctedStoppingDistance = reqStoppingDistance - TOUCHDOWN_ZONE_DISTANCE; this.bus.getPublisher().pub('oansSelectedExit', 'N/A'); Arinc429Word.toSimVarValue('L:A32NX_OANS_BTV_REQ_STOPPING_DISTANCE', correctedStoppingDistance, Arinc429SignStatusMatrix.NormalOperation); @@ -307,83 +321,145 @@ export class BrakeToVacateUtils { ctx.resetTransform(); ctx.translate(this.canvasCentreX.get(), this.canvasCentreY.get()); - // DRY stop line - const dryStopLinePoint = fractionalPointAlongLine(this.btvThresholdPosition[0], this.btvThresholdPosition[1], - this.btvOppositeThresholdPosition[0], this.btvOppositeThresholdPosition[1], - this.btvRunwayLda.get() / this.dryStoppingDistance.get()); - - const point1 = [ - 100 * Math.cos((180 - this.btvRunwayBearingTrue.get()) * MathUtils.DEGREES_TO_RADIANS) + dryStopLinePoint[0], - 100 * Math.sin((180 - this.btvRunwayBearingTrue.get()) * MathUtils.DEGREES_TO_RADIANS) + dryStopLinePoint[1], - ]; - const point2 = [ - 100 * Math.cos((-this.btvRunwayBearingTrue.get()) * MathUtils.DEGREES_TO_RADIANS) + dryStopLinePoint[0], - 100 * Math.sin((-this.btvRunwayBearingTrue.get()) * MathUtils.DEGREES_TO_RADIANS) + dryStopLinePoint[1], - ]; + const dryRunoverCondition = (TOUCHDOWN_ZONE_DISTANCE + this.dryStoppingDistance.get()) > this.btvRunwayLda.get(); + const wetRunoverCondition = (TOUCHDOWN_ZONE_DISTANCE + this.wetStoppingDistance.get()) > this.btvRunwayLda.get(); - ctx.beginPath(); - ctx.lineWidth = 18; - ctx.strokeStyle = '#000000'; - ctx.moveTo(point1[0], point1[1] * -1); - ctx.lineTo(dryStopLinePoint[0], dryStopLinePoint[1] * -1); - ctx.lineTo(point2[0], point2[1] * -1); - ctx.stroke(); - ctx.lineWidth = 10; - ctx.strokeStyle = '#ff94ff'; - ctx.moveTo(point1[0], point1[1] * -1); - ctx.lineTo(dryStopLinePoint[0], dryStopLinePoint[1] * -1); - ctx.lineTo(point2[0], point2[1] * -1); - ctx.stroke(); - - // Label - const dryLabel: Label = { - text: 'DRY', - style: LabelStyle.BtvStopLine, - position: point1, - rotation: 0, - associatedFeature: null, - }; - this.labelManager.visibleLabels.insert(dryLabel); - this.labelManager.labels.push(dryLabel); + const latDistance = 27.5 / this.getZoomLevelInverseScale(); + const strokeWidth = 3.5 / this.getZoomLevelInverseScale(); + + // DRY stop line + if (this.dryStoppingDistance.get() > 0 && !this.aircraftOnGround.get()) { + const dryStopLinePoint = fractionalPointAlongLine(this.btvThresholdPosition[0], this.btvThresholdPosition[1], + this.btvOppositeThresholdPosition[0], this.btvOppositeThresholdPosition[1], + this.btvRunwayLda.get() / (TOUCHDOWN_ZONE_DISTANCE + this.dryStoppingDistance.get())); + + const dryP1 = [ + latDistance * Math.cos((180 - this.btvRunwayBearingTrue.get()) * MathUtils.DEGREES_TO_RADIANS) + dryStopLinePoint[0], + latDistance * Math.sin((180 - this.btvRunwayBearingTrue.get()) * MathUtils.DEGREES_TO_RADIANS) + dryStopLinePoint[1], + ]; + const dryP2 = [ + latDistance * Math.cos((-this.btvRunwayBearingTrue.get()) * MathUtils.DEGREES_TO_RADIANS) + dryStopLinePoint[0], + latDistance * Math.sin((-this.btvRunwayBearingTrue.get()) * MathUtils.DEGREES_TO_RADIANS) + dryStopLinePoint[1], + ]; + + ctx.beginPath(); + ctx.lineWidth = strokeWidth + 10; + ctx.strokeStyle = '#000000'; + ctx.moveTo(dryP1[0], dryP1[1] * -1); + ctx.lineTo(dryStopLinePoint[0], dryStopLinePoint[1] * -1); + ctx.lineTo(dryP2[0], dryP2[1] * -1); + ctx.stroke(); + ctx.lineWidth = strokeWidth; + ctx.strokeStyle = dryRunoverCondition ? '#ff0000' : '#ff94ff'; + ctx.moveTo(dryP1[0], dryP1[1] * -1); + ctx.lineTo(dryStopLinePoint[0], dryStopLinePoint[1] * -1); + ctx.lineTo(dryP2[0], dryP2[1] * -1); + ctx.stroke(); + + // Label + const dryLabel: Label = { + text: 'DRY', + style: dryRunoverCondition ? LabelStyle.BtvStopLineRed : LabelStyle.BtvStopLineMagenta, + position: dryP2, + rotation: 0, + associatedFeature: null, + }; + this.labelManager.visibleLabels.insert(dryLabel); + this.labelManager.labels.push(dryLabel); + } // WET stop line - const wetStopLinePoint = fractionalPointAlongLine(this.btvThresholdPosition[0], this.btvThresholdPosition[1], - this.btvOppositeThresholdPosition[0], this.btvOppositeThresholdPosition[1], - this.btvRunwayLda.get() / this.wetStoppingDistance.get()); - - const point3 = [ - 100 * Math.cos((180 - this.btvRunwayBearingTrue.get()) * MathUtils.DEGREES_TO_RADIANS) + wetStopLinePoint[0], - 100 * Math.sin((180 - this.btvRunwayBearingTrue.get()) * MathUtils.DEGREES_TO_RADIANS) + wetStopLinePoint[1], - ]; - const point4 = [ - 100 * Math.cos((-this.btvRunwayBearingTrue.get()) * MathUtils.DEGREES_TO_RADIANS) + wetStopLinePoint[0], - 100 * Math.sin((-this.btvRunwayBearingTrue.get()) * MathUtils.DEGREES_TO_RADIANS) + wetStopLinePoint[1], - ]; + if (this.wetStoppingDistance.get() > 0 && !this.aircraftOnGround.get()) { + const wetStopLinePoint = fractionalPointAlongLine(this.btvThresholdPosition[0], this.btvThresholdPosition[1], + this.btvOppositeThresholdPosition[0], this.btvOppositeThresholdPosition[1], + this.btvRunwayLda.get() / (TOUCHDOWN_ZONE_DISTANCE + this.wetStoppingDistance.get())); + + const wetP1 = [ + latDistance * Math.cos((180 - this.btvRunwayBearingTrue.get()) * MathUtils.DEGREES_TO_RADIANS) + wetStopLinePoint[0], + latDistance * Math.sin((180 - this.btvRunwayBearingTrue.get()) * MathUtils.DEGREES_TO_RADIANS) + wetStopLinePoint[1], + ]; + const wetP2 = [ + latDistance * Math.cos((-this.btvRunwayBearingTrue.get()) * MathUtils.DEGREES_TO_RADIANS) + wetStopLinePoint[0], + latDistance * Math.sin((-this.btvRunwayBearingTrue.get()) * MathUtils.DEGREES_TO_RADIANS) + wetStopLinePoint[1], + ]; + + ctx.beginPath(); + ctx.lineWidth = strokeWidth + 10; + ctx.strokeStyle = '#000000'; + ctx.moveTo(wetP1[0], wetP1[1] * -1); + ctx.lineTo(wetStopLinePoint[0], wetStopLinePoint[1] * -1); + ctx.lineTo(wetP2[0], wetP2[1] * -1); + ctx.stroke(); + ctx.lineWidth = strokeWidth; + let labelStyle: LabelStyle; + if (!dryRunoverCondition && !wetRunoverCondition) { + ctx.strokeStyle = '#ff94ff'; // magenta + labelStyle = LabelStyle.BtvStopLineMagenta; + } else if (!dryRunoverCondition && wetRunoverCondition) { + ctx.strokeStyle = '#e68000'; // amber + labelStyle = LabelStyle.BtvStopLineAmber; + } else { + ctx.strokeStyle = '#ff0000'; + labelStyle = LabelStyle.BtvStopLineRed; + } + ctx.moveTo(wetP1[0], wetP1[1] * -1); + ctx.lineTo(wetStopLinePoint[0], wetStopLinePoint[1] * -1); + ctx.lineTo(wetP2[0], wetP2[1] * -1); + ctx.stroke(); + + // Label + const wetLabel: Label = { + text: 'WET', + style: labelStyle, + position: wetP2, + rotation: 0, + associatedFeature: null, + }; + this.labelManager.visibleLabels.insert(wetLabel); + this.labelManager.labels.push(wetLabel); + } - ctx.beginPath(); - ctx.lineWidth = 18; - ctx.strokeStyle = '#000000'; - ctx.moveTo(point3[0], point3[1] * -1); - ctx.lineTo(wetStopLinePoint[0], wetStopLinePoint[1] * -1); - ctx.lineTo(point4[0], point4[1] * -1); - ctx.stroke(); - ctx.lineWidth = 10; - ctx.strokeStyle = '#ff94ff'; - ctx.moveTo(point3[0], point3[1] * -1); - ctx.lineTo(wetStopLinePoint[0], wetStopLinePoint[1] * -1); - ctx.lineTo(point4[0], point4[1] * -1); - ctx.stroke(); - - // Label - const wetLabel: Label = { - text: 'WET', - style: LabelStyle.BtvStopLine, - position: point3, - rotation: 0, - associatedFeature: null, - }; - this.labelManager.visibleLabels.insert(wetLabel); - this.labelManager.labels.push(wetLabel); + // On ground: STOP line + if (this.liveStoppingDistance.get() > 0 && this.aircraftOnGround.get()) { + const liveRunOverCondition = this.remaininingDistToRwyEnd.get() - this.liveStoppingDistance.get() <= 0; + const stopLinePoint = fractionalPointAlongLine(this.aircraftPpos[0], this.aircraftPpos[1], + this.btvOppositeThresholdPosition[0], this.btvOppositeThresholdPosition[1], + this.remaininingDistToRwyEnd.get() / this.liveStoppingDistance.get()); + + const stopP1 = [ + latDistance * Math.cos((180 - this.btvRunwayBearingTrue.get()) * MathUtils.DEGREES_TO_RADIANS) + stopLinePoint[0], + latDistance * Math.sin((180 - this.btvRunwayBearingTrue.get()) * MathUtils.DEGREES_TO_RADIANS) + stopLinePoint[1], + ]; + const stopP2 = [ + latDistance * Math.cos((-this.btvRunwayBearingTrue.get()) * MathUtils.DEGREES_TO_RADIANS) + stopLinePoint[0], + latDistance * Math.sin((-this.btvRunwayBearingTrue.get()) * MathUtils.DEGREES_TO_RADIANS) + stopLinePoint[1], + ]; + + ctx.beginPath(); + ctx.lineWidth = strokeWidth + 10; + ctx.strokeStyle = '#000000'; + ctx.moveTo(stopP1[0], stopP1[1] * -1); + ctx.lineTo(stopLinePoint[0], stopLinePoint[1] * -1); + ctx.lineTo(stopP2[0], stopP2[1] * -1); + ctx.stroke(); + ctx.lineWidth = strokeWidth; + ctx.strokeStyle = liveRunOverCondition ? '#ff0000' : '#00ff00'; + ctx.moveTo(stopP1[0], stopP1[1] * -1); + ctx.lineTo(stopLinePoint[0], stopLinePoint[1] * -1); + ctx.lineTo(stopP2[0], stopP2[1] * -1); + ctx.stroke(); + + // Label + const stoplineLabel: Label = { + text: '', + style: liveRunOverCondition ? LabelStyle.BtvStopLineRed : LabelStyle.BtvStopLineGreen, + position: stopP1, + rotation: 0, + associatedFeature: null, + }; + this.labelManager.visibleLabels.insert(stoplineLabel); + this.labelManager.labels.push(stoplineLabel); + } } drawBtvLayer() { @@ -391,10 +467,13 @@ export class BrakeToVacateUtils { return; } + const isStopLineStyle = (s: LabelStyle) => [LabelStyle.BtvStopLineAmber, LabelStyle.BtvStopLineGreen, LabelStyle.BtvStopLineMagenta, LabelStyle.BtvStopLineRed].includes(s); + this.canvasRef.instance.getContext('2d').clearRect(0, 0, this.canvasRef.instance.width, this.canvasRef.instance.height); - this.labelManager.visibleLabels.removeAt(this.labelManager.visibleLabels.getArray().findIndex((it) => it.text === 'DRY' && it.style === LabelStyle.BtvStopLine)); - this.labelManager.visibleLabels.removeAt(this.labelManager.visibleLabels.getArray().findIndex((it) => it.text === 'WET' && it.style === LabelStyle.BtvStopLine)); - this.labelManager.labels = this.labelManager.labels.filter((it) => !((it.text === 'DRY' || it.text === 'WET') && it.style === LabelStyle.BtvStopLine)); + while (this.labelManager.visibleLabels.getArray().findIndex((it) => isStopLineStyle(it.style)) !== -1) { + this.labelManager.visibleLabels.removeAt(this.labelManager.visibleLabels.getArray().findIndex((it) => isStopLineStyle(it.style))); + } + this.labelManager.labels = this.labelManager.labels.filter((it) => !isStopLineStyle(it.style)); this.drawBtvPath(); this.drawStopLines(); } diff --git a/fbw-common/src/systems/instruments/src/OANC/BtvPublisher.ts b/fbw-common/src/systems/instruments/src/OANC/BtvPublisher.ts index 0ff4a9d0188..476c438c99b 100644 --- a/fbw-common/src/systems/instruments/src/OANC/BtvPublisher.ts +++ b/fbw-common/src/systems/instruments/src/OANC/BtvPublisher.ts @@ -16,6 +16,8 @@ export interface BtvData { dryStoppingDistance: number; /** (BTV -> OANS) Wet stopping distance */ wetStoppingDistance: number; + /** (PRIM -> OANS) Remaining stop distance on ground, used for ROP */ + stopBarDistance: number; } export enum BtvSimVars { @@ -24,6 +26,7 @@ export enum BtvSimVars { btvTurnAroundMaxReverseRaw = 'L:A32NX_BTV_TURNAROUND_MAX_REV', dryStoppingDistance = 'L:A32NX_OANS_BTV_DRY_DISTANCE_ESTIMATED', wetStoppingDistance = 'L:A32NX_OANS_BTV_WET_DISTANCE_ESTIMATED', + stopBarDistance = 'L:A32NX_OANS_BTV_STOP_BAR_DISTANCE_ESTIMATED', } export class BtvSimvarPublisher extends SimVarPublisher { @@ -33,6 +36,7 @@ export class BtvSimvarPublisher extends SimVarPublisher { ['btvTurnAroundMaxReverseRaw', { name: BtvSimVars.btvTurnAroundMaxReverseRaw, type: SimVarValueType.Number }], ['dryStoppingDistance', { name: BtvSimVars.dryStoppingDistance, type: SimVarValueType.Number }], ['wetStoppingDistance', { name: BtvSimVars.wetStoppingDistance, type: SimVarValueType.Number }], + ['stopBarDistance', { name: BtvSimVars.stopBarDistance, type: SimVarValueType.Number }], ]) public constructor(bus: ArincEventBus) { diff --git a/fbw-common/src/systems/instruments/src/OANC/Oanc.tsx b/fbw-common/src/systems/instruments/src/OANC/Oanc.tsx index b5a4220dbf4..fc55a3f46b8 100644 --- a/fbw-common/src/systems/instruments/src/OANC/Oanc.tsx +++ b/fbw-common/src/systems/instruments/src/OANC/Oanc.tsx @@ -88,7 +88,10 @@ export enum LabelStyle { BtvSelectedRunwayEnd = 'runway-end-btv-selected', BtvSelectedRunwayArrow = 'runway-arrow-btv-selected', BtvSelectedExit = 'taxiway-btv-selected', - BtvStopLine = 'btv-stop-line', + BtvStopLineMagenta = 'btv-stop-line-magenta', + BtvStopLineAmber = 'btv-stop-line-amber', + BtvStopLineRed = 'btv-stop-line-red', + BtvStopLineGreen = 'btv-stop-line-green', } export interface Label { @@ -269,7 +272,17 @@ export class Oanc extends DisplayComponent> { private readonly fmsDataStore = new FmsDataStore(this.props.bus); - private readonly btvUtils = new BrakeToVacateUtils(this.props.bus, this.labelManager, this.layerCanvasRefs[7], this.canvasCentreX, this.canvasCentreY); + private readonly btvUtils = new BrakeToVacateUtils( + this.props.bus, + this.labelManager, + this.aircraftOnGround, + this.projectedPpos, + this.layerCanvasRefs[7], + this.canvasCentreX, + this.canvasCentreY, + this.zoomLevelIndex, + this.getZoomLevelInverseScale.bind(this), + ); // eslint-disable-next-line arrow-body-style public usingPposAsReference = MappedSubject.create(([overlayNDMode, aircraftOnGround, aircraftWithinAirport]) => { @@ -277,7 +290,7 @@ export class Oanc extends DisplayComponent> { }, this.overlayNDModeSub, this.aircraftOnGround, this.aircraftWithinAirport); // eslint-disable-next-line arrow-body-style - private readonly showAircraft = this.usingPposAsReference.map((it) => it); + private readonly showAircraft = this.usingPposAsReference; private readonly aircraftX = Subject.create(0); diff --git a/fbw-common/src/systems/instruments/src/OANC/OancLabelFilter.ts b/fbw-common/src/systems/instruments/src/OANC/OancLabelFilter.ts index 1187954ab7c..6c4ec718354 100644 --- a/fbw-common/src/systems/instruments/src/OANC/OancLabelFilter.ts +++ b/fbw-common/src/systems/instruments/src/OANC/OancLabelFilter.ts @@ -33,7 +33,7 @@ export function filterLabel(label: Label, filter: OancLabelFilter, fmsDepRunway? } if (label.style === LabelStyle.BtvSelectedRunwayArrow && label.text) { return label.text.includes(btvSelectedRunway?.substring(2)); } - if (label.style === LabelStyle.BtvStopLine) { + if ([LabelStyle.BtvStopLineMagenta, LabelStyle.BtvStopLineAmber, LabelStyle.BtvStopLineRed, LabelStyle.BtvStopLineGreen].includes(label.style)) { return true; } diff --git a/fbw-common/src/systems/instruments/src/OANC/OancPlanModeCompass.tsx b/fbw-common/src/systems/instruments/src/OANC/OancPlanModeCompass.tsx index abf10d52c2b..42c81810574 100644 --- a/fbw-common/src/systems/instruments/src/OANC/OancPlanModeCompass.tsx +++ b/fbw-common/src/systems/instruments/src/OANC/OancPlanModeCompass.tsx @@ -40,7 +40,7 @@ export class OancPlanModeCompass extends DisplayComponent - {this.props.oansRange.map((range) => range / 1)} + {this.props.oansRange.map((range) => range / 2)} N diff --git a/fbw-common/src/systems/instruments/src/OANC/styles.scss b/fbw-common/src/systems/instruments/src/OANC/styles.scss index 36c83e00094..95efedfb4cb 100644 --- a/fbw-common/src/systems/instruments/src/OANC/styles.scss +++ b/fbw-common/src/systems/instruments/src/OANC/styles.scss @@ -151,14 +151,39 @@ color: $display-white; } -.oanc-label-style-btv-stop-line { +.oanc-label-style-btv-stop-line-green { + color: $display-green; +} + +.oanc-label-style-btv-stop-line-green:hover { + outline: 0px; +} + +.oanc-label-style-btv-stop-line-magenta { color: $display-magenta; } -.oanc-label-style-btv-stop-line:hover { +.oanc-label-style-btv-stop-line-magenta:hover { outline: 0px; } +.oanc-label-style-btv-stop-line-amber { + color: $display-amber; +} + +.oanc-label-style-btv-stop-line-amber:hover { + outline: 0px; +} + +.oanc-label-style-btv-stop-line-red { + color: $display-red; +} + +.oanc-label-style-btv-stop-line-red:hover { + outline: 0px; +} + + .oanc-button { padding: 10px 6px; background-color: gray;