public EHSI() { _fonts.AddFontFile("isisdigits.ttf"); _fonts.AddFontFile("ehsidigits.ttf"); InstrumentState = new InstrumentState(); Options = new Options(); }
internal static void DrawDistanceToBeacon(Graphics g, PrivateFontCollection fonts, InstrumentState instrumentState, Options options) { var fontFamily = fonts.Families[0]; if (_digitsFont == null) { _digitsFont = new ThreadLocal <Font>(() => new Font(fontFamily, 27.5f, FontStyle.Bold, GraphicsUnit.Point)); } if (_nmFont == null) { _nmFont = new ThreadLocal <Font>(() => new Font(fontFamily, 20, FontStyle.Bold, GraphicsUnit.Point)); } var distanceDigitStringFormat = new StringFormat() { Alignment = StringAlignment.Center, FormatFlags = StringFormatFlags.FitBlackBox | StringFormatFlags.NoClip | StringFormatFlags.NoWrap, LineAlignment = StringAlignment.Center, Trimming = StringTrimming.None }; var nmStringFormat = new StringFormat() { Alignment = StringAlignment.Center, FormatFlags = StringFormatFlags.FitBlackBox | StringFormatFlags.NoClip | StringFormatFlags.NoWrap, LineAlignment = StringAlignment.Center, Trimming = StringTrimming.None }; var initialState = g.Save(); var basicState = g.Save(); GraphicsUtil.RestoreGraphicsState(g, ref basicState); var distanceToBeaconString = $"{instrumentState.DistanceToBeaconNauticalMiles:000.0}"; var hundredsDigit = distanceToBeaconString.Substring(0, 1); var tensDigit = distanceToBeaconString.Substring(1, 1); var onesDigit = distanceToBeaconString.Substring(2, 1); var tenthsDigit = distanceToBeaconString.Substring(4, 1); const float digitWidth = 22; const float digitHeight = 32; const float digitSeparationPixels = -4; GraphicsUtil.RestoreGraphicsState(g, ref basicState); var hundredsRect = new RectangleF(12, 8, digitWidth, digitHeight); var tensRect = new RectangleF(hundredsRect.X + digitWidth + digitSeparationPixels, hundredsRect.Y, digitWidth, digitHeight); var onesRect = new RectangleF(tensRect.X + digitWidth + digitSeparationPixels, tensRect.Y, digitWidth, digitHeight); var tenthsRect = new RectangleF(onesRect.X + digitWidth + 4, onesRect.Y, digitWidth, digitHeight); g.DrawStringFast(hundredsDigit, _digitsFont.Value, Brushes.White, hundredsRect, distanceDigitStringFormat); g.DrawStringFast(tensDigit, _digitsFont.Value, Brushes.White, tensRect, distanceDigitStringFormat); g.DrawStringFast(onesDigit, _digitsFont.Value, Brushes.White, onesRect, distanceDigitStringFormat); g.FillRectangle(Brushes.White, tenthsRect); g.DrawStringFast(tenthsDigit, _digitsFont.Value, Brushes.Black, tenthsRect, distanceDigitStringFormat); if (instrumentState.DmeInvalidFlag) { var dmeInvalidFlagUpperLeft = new PointF(hundredsRect.X, hundredsRect.Y + 8); var dmeInvalidFlagSize = new SizeF(tenthsRect.X + tenthsRect.Width - hundredsRect.X, 16); var dmeInvalidFlagRect = new RectangleF(dmeInvalidFlagUpperLeft, dmeInvalidFlagSize); var redFlagColor = Color.FromArgb(224, 43, 48); using (var redFlagBrush = new SolidBrush(redFlagColor)) { g.FillRectangle(redFlagBrush, dmeInvalidFlagRect); } } var nmRect = new RectangleF(hundredsRect.X, 45, 30, 20); g.DrawStringFast("NM", _nmFont.Value, Brushes.White, nmRect, nmStringFormat); GraphicsUtil.RestoreGraphicsState(g, ref initialState); }
internal static void DrawCompassRose(Graphics g, RectangleF outerBounds, PrivateFontCollection fonts, InstrumentState instrumentState, Options options) { var initialState = g.Save(); var basicState = g.Save(); var majorHeadingDigitStringFormat = new StringFormat { Alignment = StringAlignment.Center, FormatFlags = StringFormatFlags.FitBlackBox | StringFormatFlags.NoClip | StringFormatFlags.NoWrap, LineAlignment = StringAlignment.Center, Trimming = StringTrimming.None }; if (_majorHeadingDigitFont == null) { var fontFamily = fonts.Families[1]; _majorHeadingDigitFont = new ThreadLocal <Font>(() => new Font(fontFamily, 27.5f, FontStyle.Bold, GraphicsUnit.Point)); } var linePen = new Pen(Color.White) { Width = 3 }; GraphicsUtil.RestoreGraphicsState(g, ref basicState); const float majorHeadingLineLength = 28; const float minorHeadingLineLength = majorHeadingLineLength / 2.0f; const float majorHeadingLegendLayoutRectangleHeight = 30; const float majorHeadingLegendLayoutRectangleWidth = 30; using (var majorHeadingBrush = new SolidBrush(Color.White)) { var innerBounds = new RectangleF(outerBounds.X, outerBounds.Y, outerBounds.Width, outerBounds.Height); const float marginWidth = 30f; innerBounds.Inflate(-marginWidth, -marginWidth); for (var i = 0; i < 360; i += 45) { GraphicsUtil.RestoreGraphicsState(g, ref basicState); g.TranslateTransform(marginWidth, marginWidth); g.TranslateTransform(innerBounds.Width / 2.0f, innerBounds.Height / 2.0f); g.RotateTransform(i); g.TranslateTransform(-innerBounds.Width / 2.0f, -innerBounds.Height / 2.0f); const float separationPixels = 2; //draw 45-degree outer ticks g.DrawLineFast( linePen, new PointF(innerBounds.Width / 2.0f, -separationPixels), i % 90 == 0 ? new PointF(innerBounds.Width / 2.0f, -(minorHeadingLineLength * 1.5f + separationPixels)) : new PointF(innerBounds.Width / 2.0f, -(majorHeadingLineLength + separationPixels))); GraphicsUtil.RestoreGraphicsState(g, ref basicState); } for (var i = 0; i < 360; i++) { GraphicsUtil.RestoreGraphicsState(g, ref basicState); g.TranslateTransform(outerBounds.Width / 2.0f, outerBounds.Height / 2.0f); g.RotateTransform(-instrumentState.MagneticHeadingDegrees); g.TranslateTransform(-outerBounds.Width / 2.0f, -outerBounds.Height / 2.0f); g.TranslateTransform(marginWidth, marginWidth); g.TranslateTransform(innerBounds.Width / 2.0f, innerBounds.Height / 2.0f); g.RotateTransform(i); g.TranslateTransform(-innerBounds.Width / 2.0f, -innerBounds.Height / 2.0f); if (i % 10 == 0) { g.DrawLineFast(linePen, new PointF(innerBounds.Width / 2.0f, 0), new PointF(innerBounds.Width / 2.0f, majorHeadingLineLength)); } else if (i % 5 == 0) { g.DrawLineFast(linePen, new PointF(innerBounds.Width / 2.0f, 0), new PointF(innerBounds.Width / 2.0f, minorHeadingLineLength)); } if (i % 30 == 0) { var majorHeadingLegendText = $"{i / 10:##}"; if (i == 90) { majorHeadingLegendText = "E"; } else if (i == 180) { majorHeadingLegendText = "S"; } else if (i == 270) { majorHeadingLegendText = "W"; } else if (i == 0) { majorHeadingLegendText = "N"; } var majorHeadingLegendLayoutRectangle = new RectangleF( innerBounds.Width / 2.0f - majorHeadingLegendLayoutRectangleWidth / 2.0f, majorHeadingLegendLayoutRectangleHeight / 2.0f, majorHeadingLegendLayoutRectangleWidth, majorHeadingLegendLayoutRectangleHeight); majorHeadingLegendLayoutRectangle.Offset(0, 18); g.DrawStringFast(majorHeadingLegendText, _majorHeadingDigitFont.Value, majorHeadingBrush, majorHeadingLegendLayoutRectangle, majorHeadingDigitStringFormat); } GraphicsUtil.RestoreGraphicsState(g, ref basicState); } GraphicsUtil.RestoreGraphicsState(g, ref initialState); } }
internal static void DrawBearingToBeaconIndicator(Graphics g, RectangleF outerBounds, InstrumentState instrumentState) { using (var NeedleBrush = new SolidBrush(NeedleColor)) using (var needlePen = new Pen(NeedleColor) { Width = 4 }) { var basicState = g.Save(); g.TranslateTransform(outerBounds.Width / 2.0f, outerBounds.Height / 2.0f); g.RotateTransform(-instrumentState.MagneticHeadingDegrees); g.RotateTransform(instrumentState.BearingToBeaconDegrees); g.TranslateTransform(-outerBounds.Width / 2.0f, -outerBounds.Height / 2.0f); g.TranslateTransform(outerBounds.Width / 2.0f, 0); const float bearingTriangleWidth = 23; const float bearingTriangleHeight = 25; var bearingTriangleTop = new PointF(0, 5); var bearingTriangleLeft = new PointF(-bearingTriangleWidth / 2.0f, bearingTriangleTop.Y + bearingTriangleHeight); var bearingTriangleRight = new PointF(bearingTriangleWidth / 2.0f, bearingTriangleTop.Y + bearingTriangleHeight); g.FillPolygon(NeedleBrush, new[] { bearingTriangleTop, bearingTriangleLeft, bearingTriangleRight }); const float bearingLineTopHeight = 23; var bearingLineTopTop = new PointF(bearingTriangleTop.X, bearingTriangleLeft.Y); var bearingLineTopBottom = new PointF(bearingLineTopTop.X, bearingLineTopTop.Y + bearingLineTopHeight); g.DrawLineFast(needlePen, bearingLineTopTop, bearingLineTopBottom); const float bearingLineBottomHeight = 37; var bearingLineBottomTop = new PointF(bearingTriangleTop.X, 455); var bearingLineBottomBottom = new PointF(bearingTriangleTop.X, bearingLineBottomTop.Y + bearingLineBottomHeight); g.DrawLineFast(needlePen, bearingLineBottomTop, bearingLineBottomBottom); GraphicsUtil.RestoreGraphicsState(g, ref basicState); } }
internal static void DrawDesiredCourse(Graphics g, RectangleF outerBounds, PrivateFontCollection fonts, InstrumentState instrumentState, Options options) { var fontFamily = fonts.Families[0]; if (_digitsFont == null) { _digitsFont = new ThreadLocal <Font>(() => new Font(fontFamily, 27.5f, FontStyle.Bold, GraphicsUnit.Point)); } if (_crsFont == null) { _crsFont = new ThreadLocal <Font>(() => new Font(fontFamily, 20, FontStyle.Bold, GraphicsUnit.Point)); } var desiredCourseDigitStringFormat = new StringFormat { Alignment = StringAlignment.Center, FormatFlags = StringFormatFlags.FitBlackBox | StringFormatFlags.NoClip | StringFormatFlags.NoWrap, LineAlignment = StringAlignment.Center, Trimming = StringTrimming.None }; var crsStringFormat = new StringFormat() { Alignment = StringAlignment.Far, FormatFlags = StringFormatFlags.FitBlackBox | StringFormatFlags.NoClip | StringFormatFlags.NoWrap, LineAlignment = StringAlignment.Center, Trimming = StringTrimming.None }; var initialState = g.Save(); var basicState = g.Save(); GraphicsUtil.RestoreGraphicsState(g, ref basicState); var desiredCourse = $"{instrumentState.DesiredCourseDegrees:000.0}"; var hundredsDigit = desiredCourse.Substring(0, 1); var tensDigit = desiredCourse.Substring(1, 1); var onesDigit = desiredCourse.Substring(2, 1); const float digitWidth = 22; const float digitHeight = 32; const float digitSeparationPixels = -2; GraphicsUtil.RestoreGraphicsState(g, ref basicState); const float margin = 8; var hundredsRect = new RectangleF(outerBounds.Width - margin - (digitWidth + digitSeparationPixels) * 3, margin, digitWidth, digitHeight); var tensRect = new RectangleF(hundredsRect.X + digitWidth + digitSeparationPixels, hundredsRect.Y, digitWidth, digitHeight); var onesRect = new RectangleF(tensRect.X + digitWidth + digitSeparationPixels, tensRect.Y, digitWidth, digitHeight); g.DrawStringFast(hundredsDigit, _digitsFont.Value, Brushes.White, hundredsRect, desiredCourseDigitStringFormat); g.DrawStringFast(tensDigit, _digitsFont.Value, Brushes.White, tensRect, desiredCourseDigitStringFormat); g.DrawStringFast(onesDigit, _digitsFont.Value, Brushes.White, onesRect, desiredCourseDigitStringFormat); var crsRect = new RectangleF(hundredsRect.X, 45, (digitWidth + digitSeparationPixels) * 3, 20); g.DrawStringFast("CRS", _crsFont.Value, Brushes.White, crsRect, crsStringFormat); GraphicsUtil.RestoreGraphicsState(g, ref initialState); }
internal static void DrawInstrumentMode(Graphics g, RectangleF outerBounds, PrivateFontCollection fonts, InstrumentState instrumentState) { if (_labelFont == null) { var fontFamily = fonts.Families[0]; _labelFont = new ThreadLocal <Font>(() => new Font(fontFamily, 25, FontStyle.Bold, GraphicsUnit.Point)); } var labelStringFormat = new StringFormat { Alignment = StringAlignment.Center, FormatFlags = StringFormatFlags.FitBlackBox | StringFormatFlags.NoClip | StringFormatFlags.NoWrap, LineAlignment = StringAlignment.Center, Trimming = StringTrimming.None }; const float letterHeight = 20; const float margin = 8; const float labelWidth = 50; var howLongSinceInstrumentModeChanged = DateTime.UtcNow.Subtract(instrumentState.WhenInstrumentModeLastChanged); if (howLongSinceInstrumentModeChanged.TotalMilliseconds <= 2000) { var toDisplay = string.Empty; switch (instrumentState.InstrumentMode) { case InstrumentModes.Unknown: break; case InstrumentModes.PlsTacan: toDisplay = "PLS/TACAN"; break; case InstrumentModes.Tacan: toDisplay = "TACAN"; break; case InstrumentModes.Nav: toDisplay = "NAV"; break; case InstrumentModes.PlsNav: toDisplay = "PLS/NAV"; break; } if (!instrumentState.ShowBrightnessLabel) { CenterLabelRenderer.DrawCenterLabel(g, outerBounds, toDisplay, fonts); } } //draw PLS label if (instrumentState.InstrumentMode == InstrumentModes.PlsNav || instrumentState.InstrumentMode == InstrumentModes.PlsTacan) { var plsLabelRect = new RectangleF(outerBounds.Width * 0.25f, outerBounds.Height - letterHeight - margin, labelWidth, letterHeight); g.DrawStringFast("PLS", _labelFont.Value, Brushes.White, plsLabelRect, labelStringFormat); } if (instrumentState.InstrumentMode == InstrumentModes.PlsNav || instrumentState.InstrumentMode == InstrumentModes.Nav) { var navLabelRect = new RectangleF(outerBounds.Width * 0.7f, outerBounds.Height - letterHeight - margin, labelWidth, letterHeight); g.DrawStringFast("NAV", _labelFont.Value, Brushes.White, navLabelRect, labelStringFormat); } if (instrumentState.InstrumentMode == InstrumentModes.PlsTacan || instrumentState.InstrumentMode == InstrumentModes.Tacan) { var tacanLabelRect = new RectangleF(outerBounds.Width * 0.7f, outerBounds.Height - letterHeight - margin, labelWidth, letterHeight); g.DrawStringFast("TCN", _labelFont.Value, Brushes.White, tacanLabelRect, labelStringFormat); } }
internal static void DrawCourseDeviationNeedles(Graphics g, RectangleF outerBounds, InstrumentState instrumentState) { var initialState = g.Save(); var redFlagColor = Color.FromArgb(224, 43, 48); var needleColor = Color.FromArgb(102, 190, 157); using (var redFlagBrush = new SolidBrush(redFlagColor)) using (var needleBrush = new SolidBrush(needleColor)) using (var needlePen = new Pen(needleColor)) { const float pointerNeedleThickWidth = 8; const float pointerNeedleThinWidth = 3; const float maxDeviationX = 110; const float dotHeight = 15; var dotY = (outerBounds.Height - dotHeight) / 2.0f; const float leftInnerDotX = -maxDeviationX / 2.0f - dotHeight / 2.0f; const float leftOuterDotX = -maxDeviationX - dotHeight / 2.0f; const float rightInnerDotX = maxDeviationX / 2.0f - dotHeight / 2.0f; const float rightOuterDotX = maxDeviationX - dotHeight / 2.0f; const float topIndicatorLineHeight = 50; const float coursePointerTriangleWidth = 30; const float coursePointerTriangleHeight = 20; const float pointerNeedleThickHeightTop = 40; const float cdiNeedleHeight = 198; var deviationTranslateX = maxDeviationX * Math.Sign(instrumentState.CourseDeviationDegrees) * (Math.Abs(instrumentState.CourseDeviationDegrees) / Math.Abs(instrumentState.CourseDeviationLimitDegrees)); const float pointerNeedleThickHeightBottom = 60; const float bottomIndicatorLineHeight = 50; var courseNeedleTopLineTop = new PointF(0, 43); var courseNeedleTopLineBottom = new PointF(0, courseNeedleTopLineTop.Y + topIndicatorLineHeight); var coursePointerTriangleLeft = new PointF(courseNeedleTopLineBottom.X - coursePointerTriangleWidth / 2.0f, courseNeedleTopLineBottom.Y + coursePointerTriangleHeight); var coursePointerTriangleRight = new PointF(courseNeedleTopLineBottom.X + coursePointerTriangleWidth / 2.0f, coursePointerTriangleLeft.Y); var coursePointerTriangleTop = new PointF(0, courseNeedleTopLineBottom.Y - 2); var pointerNeedleThickTopTop = new PointF(0, coursePointerTriangleLeft.Y); var pointerNeedleThickTopBottom = new PointF(0, pointerNeedleThickTopTop.Y + pointerNeedleThickHeightTop); var deviationInvalidFlagRect = new RectangleF(new PointF(-80, pointerNeedleThickTopBottom.Y), new SizeF(60, 30)); var toFromFlagHeight = deviationInvalidFlagRect.Height; var toFromFlagWidth = deviationInvalidFlagRect.Height; var cdiLineTop = new PointF(0, pointerNeedleThickTopBottom.Y + 2); var cdiLineBottom = new PointF(0, cdiLineTop.Y + cdiNeedleHeight); var pointerNeedleThickBottomTop = new PointF(0, cdiLineBottom.Y + 2); var pointerNeedleThickBottomBottom = new PointF(0, pointerNeedleThickBottomTop.Y + pointerNeedleThickHeightBottom); var courseNeedleBottomLineTop = new PointF(0, pointerNeedleThickBottomBottom.Y); var courseNeedleBottomLineBottom = new PointF(0, courseNeedleBottomLineTop.Y + bottomIndicatorLineHeight); var toFlagTop = new PointF(maxDeviationX / 2.0f, pointerNeedleThickTopBottom.Y); var toFlagLeft = new PointF(toFlagTop.X - toFromFlagWidth / 2.0f, toFlagTop.Y + toFromFlagHeight); var toFlagRight = new PointF(toFlagTop.X + toFromFlagWidth / 2.0f, toFlagTop.Y + toFromFlagHeight); var fromFlagBottom = new PointF(maxDeviationX / 2.0f, pointerNeedleThickBottomTop.Y); var fromFlagLeft = new PointF(fromFlagBottom.X - toFromFlagWidth / 2.0f, fromFlagBottom.Y - toFromFlagHeight); var fromFlagRight = new PointF(fromFlagBottom.X + toFromFlagWidth / 2.0f, fromFlagBottom.Y - toFromFlagHeight); g.TranslateTransform(outerBounds.Width / 2.0f, outerBounds.Height / 2.0f); g.RotateTransform(-instrumentState.MagneticHeadingDegrees); g.RotateTransform(instrumentState.DesiredCourseDegrees); g.TranslateTransform(-outerBounds.Width / 2.0f, -outerBounds.Height / 2.0f); g.TranslateTransform(outerBounds.Width / 2.0f, 0); //draw deviation dots g.FillEllipse(Brushes.White, new RectangleF(leftInnerDotX, dotY, dotHeight, dotHeight)); g.FillEllipse(Brushes.White, new RectangleF(leftOuterDotX, dotY, dotHeight, dotHeight)); g.FillEllipse(Brushes.White, new RectangleF(rightInnerDotX, dotY, dotHeight, dotHeight)); g.FillEllipse(Brushes.White, new RectangleF(rightOuterDotX, dotY, dotHeight, dotHeight)); //draw thin line on top of pointer arrow needlePen.Width = pointerNeedleThinWidth; g.DrawLineFast(needlePen, courseNeedleTopLineTop, courseNeedleTopLineBottom); //draw pointer arrow g.FillPolygon(needleBrush, new[] { coursePointerTriangleTop, coursePointerTriangleLeft, coursePointerTriangleRight }); //draw thick line just below pointer arrow needlePen.Width = pointerNeedleThickWidth; g.DrawLineFast(needlePen, pointerNeedleThickTopTop, pointerNeedleThickTopBottom); if (instrumentState.DeviationInvalidFlag) { //draw deviation invalid flag g.FillRectangle(redFlagBrush, deviationInvalidFlagRect); } //draw CDI needle needlePen.Width = pointerNeedleThickWidth; if (instrumentState.DeviationInvalidFlag) { needlePen.DashPattern = new[] { 2, 1.75f } } ; g.TranslateTransform(deviationTranslateX, 0); g.DrawLineFast(needlePen, cdiLineTop, cdiLineBottom); g.TranslateTransform(-deviationTranslateX, 0); needlePen.DashStyle = DashStyle.Solid; //draw thick line just below CDI needle needlePen.Width = pointerNeedleThickWidth; g.DrawLineFast(needlePen, pointerNeedleThickBottomTop, pointerNeedleThickBottomBottom); //draw thin line indicating reciprocal-of-course needlePen.Width = pointerNeedleThinWidth; g.DrawLineFast(needlePen, courseNeedleBottomLineTop, courseNeedleBottomLineBottom); if (instrumentState.ShowToFromFlag) { if (instrumentState.ToFlag) { g.FillPolygon(Brushes.White, new[] { toFlagTop, toFlagLeft, toFlagRight }); } if (instrumentState.FromFlag) { g.FillPolygon(Brushes.White, new[] { fromFlagBottom, fromFlagLeft, fromFlagRight }); } } GraphicsUtil.RestoreGraphicsState(g, ref initialState); } } }
internal static void DrawDesiredHeadingBug(Graphics g, RectangleF outerBounds, InstrumentState instrumentState) { var basicState = g.Save(); g.TranslateTransform(outerBounds.Width / 2.0f, outerBounds.Height / 2.0f); g.RotateTransform(-instrumentState.MagneticHeadingDegrees); g.RotateTransform(instrumentState.DesiredHeadingDegrees); g.TranslateTransform(-outerBounds.Width / 2.0f, -outerBounds.Height / 2.0f); const float headingBugSquareSize = 20; const float headingBugGapBetweenSquares = 5; const float headingBugSquareTop = 18; var centerX = outerBounds.X + outerBounds.Width / 2.0f; var leftHeadingBugSquareLocation = new PointF(centerX - headingBugSquareSize - headingBugGapBetweenSquares / 2.0f, headingBugSquareTop); var rightHeadingBugSquareLocation = new PointF(centerX + headingBugGapBetweenSquares / 2.0f, headingBugSquareTop); var headingBugLeftSquare = new RectangleF(leftHeadingBugSquareLocation, new SizeF(headingBugSquareSize, headingBugSquareSize)); var headingBugRightSquare = new RectangleF(rightHeadingBugSquareLocation, new SizeF(headingBugSquareSize, headingBugSquareSize)); var headingBugColor = Color.FromArgb(248, 238, 153); using (var headingBugBrush = new SolidBrush(headingBugColor)) { g.FillRectangle(headingBugBrush, headingBugLeftSquare); g.FillRectangle(headingBugBrush, headingBugRightSquare); } GraphicsUtil.RestoreGraphicsState(g, ref basicState); }