private bool AnimatePlane()
        {
            // Skip doing anything if animation is paused
            if (!_animationTimer)
            {
                return(true);
            }

            // Get the next position; % prevents going out of bounds even if the keyframe value is
            //     changed unexpectedly (e.g. due to user interaction with the progress slider).
            MissionFrame currentFrame = _missionData[_keyframe % _frameCount];

            // Update the UI
            double missionProgress = _keyframe / (double)_frameCount;

            // This is needed because the event could be running on a non-UI thread
            Device.BeginInvokeOnMainThread(() =>
            {
                // Update the progress slider; temporarily remove event subscription to avoid feedback loop
                MissionProgressBar.ValueChanged -= MissionProgressOnSeek;
                MissionProgressBar.Value         = missionProgress * 100;
                MissionProgressBar.ValueChanged += MissionProgressOnSeek;

                // Update stats display
                AltitudeLabel.Text = $"{currentFrame.Elevation:F}m";
                HeadingLabel.Text  = $"{currentFrame.Heading:F}°";
                PitchLabel.Text    = $"{currentFrame.Pitch:F}°";
                RollLabel.Text     = $"{currentFrame.Pitch:F}°";
            });

            // Update plane's position
            _plane3D.Geometry = currentFrame.ToMapPoint();
            _plane3D.Attributes["HEADING"] = currentFrame.Heading;
            _plane3D.Attributes["PITCH"]   = currentFrame.Pitch;
            _plane3D.Attributes["ROLL"]    = currentFrame.Roll;

            // Update the inset map; plane symbol position
            _plane2D.Geometry = currentFrame.ToMapPoint();
            // Update inset's viewpoint and heading
            Viewpoint vp = new Viewpoint(currentFrame.ToMapPoint(), InsetMapView.MapScale,
                                         360 + (float)currentFrame.Heading);

            InsetMapView.SetViewpoint(vp);

            // Update the keyframe. This advances the animation
            _keyframe++;

            // Restart the animation if it has finished
            if (_keyframe >= _frameCount)
            {
                _keyframe = 0;
            }

            // Keep the animation event going
            return(true);
        }
 public void UpdateStatsDisplay(MissionFrame currentFrame, double missionProgress)
 {
     // Skip if stats display hasn't been shown yet.
     if (_altitudeLabel == null)
     {
         return;
     }
     _altitudeLabel.Text = $"{currentFrame.Elevation:F} m";
     _headingLabel.Text  = $"{currentFrame.Heading:F}\u00B0"; // \u00b0 is the degree symbol
     _pitchLabel.Text    = $"{currentFrame.Pitch:F}\u00B0";
     _rollLabel.Text     = $"{currentFrame.Roll:F}\u00B0";
     _progressLabel.Text = $"{missionProgress * 100:F}%";
 }
        private void AnimatePlane(object sender, ElapsedEventArgs elapsedEventArgs)
        {
            // Get the next position; % prevents going out of bounds even if the keyframe value is
            //     changed unexpectedly (e.g. due to user interaction with the progress slider).
            MissionFrame currentFrame = _missionData[_keyframe % _frameCount];

            // Update the UI
            double missionProgress = _keyframe / (double)_frameCount;

            // This is needed because the event could be running on a non-UI thread
            RunOnUiThread(() =>
            {
                // Update the progress slider; temporarily remove event subscription to prevent feedback loop
                _missionProgressBar.ProgressChanged -= MissionProgressOnSeek;
                _missionProgressBar.Progress         = (int)(missionProgress * 100);
                _missionProgressBar.ProgressChanged += MissionProgressOnSeek;

                // Update stats display
                _altitudeTextView.Text = $"{currentFrame.Elevation:F}m";
                _headingTextView.Text  = $"{currentFrame.Heading:F}°";
                _pitchTextView.Text    = $"{currentFrame.Pitch:F}°";
                _rollTextView.Text     = $"{currentFrame.Roll:F}°";
            });

            // Update plane's position
            _plane3D.Geometry = currentFrame.ToMapPoint();
            _plane3D.Attributes["HEADING"] = currentFrame.Heading;
            _plane3D.Attributes["PITCH"]   = currentFrame.Pitch;
            _plane3D.Attributes["ROLL"]    = currentFrame.Roll;

            // Update the inset map; plane symbol position
            _plane2D.Geometry = currentFrame.ToMapPoint();
            // Update inset's viewpoint and heading
            Viewpoint vp = new Viewpoint(currentFrame.ToMapPoint(), _insetMapView.MapScale,
                                         360 + (float)currentFrame.Heading);

            _insetMapView.SetViewpoint(vp);

            // Update the keyframe. This advances the animation
            _keyframe++;

            // Restart the animation if it has finished
            if (_keyframe >= _frameCount)
            {
                _keyframe = 0;
            }
        }
        private void AnimatePlane()
        {
            // Get the next position; % prevents going out of bounds even if the keyframe value is
            //     changed unexpectedly (e.g. due to user interaction with the progress slider).
            MissionFrame currentFrame = _missionData[_keyframe];

            // Update the UI.
            double missionProgress = _keyframe / (double)_frameCount;

            // This is needed because the event could be running on a non-UI thread.
            InvokeOnMainThread(() =>
            {
                // Update stats display.
                _altitudeLabel.Text = $"{currentFrame.Elevation:F}m";
                _headingLabel.Text  = $"{currentFrame.Heading:F}°";
                _pitchLabel.Text    = $"{currentFrame.Pitch:F}°";
                _rollLabel.Text     = $"{currentFrame.Roll:F}°";
                _progressLabel.Text = $"{missionProgress * 100:F}%";
            });

            // Update plane's position.
            _plane3D.Geometry = currentFrame.ToMapPoint();
            _plane3D.Attributes["HEADING"] = currentFrame.Heading;
            _plane3D.Attributes["PITCH"]   = currentFrame.Pitch;
            _plane3D.Attributes["ROLL"]    = currentFrame.Roll;

            // Update the inset map; plane symbol position.
            _plane2D.Geometry = currentFrame.ToMapPoint();

            // Update inset's viewpoint and heading.
            Viewpoint vp = new Viewpoint(currentFrame.ToMapPoint(), _insetMapView.MapScale,
                                         360 + (float)currentFrame.Heading);

            _insetMapView.SetViewpoint(vp);

            // Update the keyframe. This advances the animation.
            _keyframe++;

            // Restart the animation if it has finished.
            if (_keyframe >= _frameCount)
            {
                _keyframe = 0;
            }
        }