Пример #1
0
        static void Main(string[] args)
        {
            // 显示版本号
            Console.WriteLine("DisplayVersion: {0}", StkComponentsCore.DisplayVersion);
            Console.WriteLine("Version: {0}", StkComponentsCore.Version);

            // 显示当前时刻地球质心和月球质心之间的距离
            DateTime         now   = DateTime.Now;
            EarthCentralBody earth = CentralBodiesFacet.GetFromContext().Earth;
            MoonCentralBody  moon  = CentralBodiesFacet.GetFromContext().Moon;
            var    vector          = new VectorTrueDisplacement(earth.CenterOfMassPoint, moon.CenterOfMassPoint);
            double distance        = vector.GetEvaluator().Evaluate(new JulianDate(now)).Magnitude;

            Console.WriteLine("当前时间:{0:yyyy-MM-dd HH:mm:ss.fff},当前日月距离:{1:0.000}千米", now, distance / 1000);
            Console.Read();
        }
Пример #2
0
        /// <summary>
        /// Method to calculate all the data required for the graphs.
        /// </summary>
        private void OnAddReceiverClick(object sender, EventArgs e)
        {
            #region CreateAndConfigureReceiver
            // Let's create the GPSReceiver. The receiver stores many properties and has a defined location. This location
            // is the point of reference for visibility calculations.
            receiver = new GpsReceiver();

            // add receiver to the tree
            TreeNode newNode = new TreeNode(Localization.Receiver);
            rootNode.Nodes.Add(newNode);

            // Easy reference to Earth Central body used to initialize the ElevationAngleAccessConstraint and
            // to calculate the Az/El/Range Data.
            EarthCentralBody earth = CentralBodiesFacet.GetFromContext().Earth;

            // set the receiver properties based on user selections
            // The receiver has a receiver FrontEnd that contains the visibility and tracking constraints
            // Be sure to convert your angles to Radians!
            double minimumAngle = Trig.DegreesToRadians(Double.Parse(MaskAngle.Text));
            receiver.ReceiverConstraints.Clear();
            receiver.ReceiverConstraints.Add(new ElevationAngleConstraint(earth, minimumAngle));
            receiver.NumberOfChannels = (int)NumberOfChannels.Value;
            receiver.NoiseModel       = new ConstantGpsReceiverNoiseModel(0.8);

            // The receiver's methods of reducing the number of visible satellites to the limit imposed by the number of channels
            if (BestNSolType.Checked)
            {
                receiver.ReceiverSolutionType = GpsReceiverSolutionType.BestN;
            }
            else
            {
                receiver.ReceiverSolutionType = GpsReceiverSolutionType.AllInView;
            }

            // create a new location for the receiver by using the Cartographic type from AGI.Foundation.Coordinates
            // again, remember to convert from degrees to Radians! (except the height of course)
            Cartographic position = new Cartographic(Trig.DegreesToRadians(double.Parse(Longitude.Text)),
                                                     Trig.DegreesToRadians(double.Parse(Latitude.Text)),
                                                     double.Parse(ReceiverHeight.Text));

            // Now create an antenna for the GPS receiver. We specify the location of the antenna by assigning a
            // PointCartographic instance to the LocationPoint property. We specify that the antenna should be oriented
            // according to the typically-used East-North-Up axes by assigning an instance of AxesEastNorthUp to
            // the OrientationAxes property. While the orientation of the antenna won't affect which satellites are visible
            // or tracked in this case, it will affect the DOP values. For example, the EDOP value can be found in
            // DilutionOfPrecision.X, but only if we've configured the antenna to use East-North-Up axes.
            PointCartographic antennaLocationPoint = new PointCartographic(earth, position);
            Platform          antenna = new Platform
            {
                LocationPoint   = antennaLocationPoint,
                OrientationAxes = new AxesEastNorthUp(earth, antennaLocationPoint)
            };
            receiver.Antenna = antenna;

            #endregion

            // update the tree to reflect the correct receiver info
            newNode.Nodes.Add(new TreeNode(string.Format(Localization.FixedMaskAngle + "= {0:0.00} " + Localization.degrees, MaskAngle.Text)));
            newNode.Nodes.Add(new TreeNode(string.Format(Localization.NumberOfChannels + "= {0}", NumberOfChannels.Value)));
            newNode.Nodes.Add(new TreeNode(string.Format(Localization.SolutionType + "= {0}", receiver.ReceiverSolutionType == GpsReceiverSolutionType.AllInView ? Localization.AllInView : Localization.BestN)));
            newNode.Nodes.Add(new TreeNode(string.Format(Localization.Latitude + "= {0:0.000000} " + Localization.degrees, Latitude.Text)));
            newNode.Nodes.Add(new TreeNode(string.Format(Localization.Longitude + "= {0:0.000000} " + Localization.degrees, Longitude.Text)));
            newNode.Nodes.Add(new TreeNode(string.Format(Localization.Height + "= {0:0.000} " + Localization.meters, ReceiverHeight.Text)));

            rootNode.Expand();
            newNode.Expand();

            #region CalculateDataForGraphs

            // Now, we'll open the almanac
            SemAlmanac almanac;
            using (Stream stream = openAlmanacDialog.OpenFile())
                using (StreamReader reader = new StreamReader(stream))
                {
                    // Read the SEM almanac from an almanac stream reader.
                    almanac = SemAlmanac.ReadFrom(reader, 1);
                }

            // Now create a PlatformCollection to hold GpsSatellite object instances. The SemAlmanac method CreateSatellitesFromRecords returns
            // just such a collection. We'll use this set of satellites as the set from which we'll try and track. There is a
            // GpsSatellite object for each satellite specified in the almanac.
            PlatformCollection gpsSatellites = almanac.CreateSatelliteCollection();

            // We provide the receiver with the complete set of gpsSatellites to consider for visibility calculations.
            // This is usually all SVs defined in the almanac - however you may want to remove SVs that aren't healthy. This can
            // be done by creating the gpsSatellites collection above using another version of the CreateSatellitesFromRecords method that
            // takes a SatelliteOutageFileReader.
            receiver.NavigationSatellites = gpsSatellites;

            // Optimization opportunity: Add the following code in a thread. This will help for long duration analyses.

            // Now that we have the receiver and location setup, we need to evaluate all the pertinent data.
            // using a SatelliteTrackingEvaluator, we can track satellites and using a DOP Evaluator,
            // we can calculate DOP at a specified time.
            // The receiver's GetSatelliteTrackingEvaluator method will provide a SatelliteTrackingEvaluator for you.
            // Similarly, the GetDilutionOfPrecisionEvaluator provides the DOP evaluator.
            // We create all evaluators in the same EvaluatorGroup for the best performance.

            EvaluatorGroup    group = new EvaluatorGroup();
            Evaluator <int[]> satTrackingEvaluator       = receiver.GetSatelliteTrackingIndexEvaluator(group);
            Evaluator <DilutionOfPrecision> dopEvaluator = receiver.GetDilutionOfPrecisionEvaluator(group);

            // We also need to create an evaluator to compute Azimuth/Elevation for each of the SVs
            MotionEvaluator <AzimuthElevationRange>[] aerEvaluators = new MotionEvaluator <AzimuthElevationRange> [gpsSatellites.Count];
            for (int i = 0; i < gpsSatellites.Count; ++i)
            {
                Platform satellite            = receiver.NavigationSatellites[i];
                VectorTrueDisplacement vector = new VectorTrueDisplacement(antenna.LocationPoint, satellite.LocationPoint);
                aerEvaluators[i] = earth.GetAzimuthElevationRangeEvaluator(vector, group);
            }

            // First we'll initialize the data structures used to plot the data
            for (int i = 0; i < DOPData.Length; i++)
            {
                // PointPairList is defined in the ZedGraph reference
                DOPData[i] = new PointPairList();
            }

            // We need to know which time standard to use here. If the user has specified that GPS time is to be used
            // we need to create the JulianDates with the GlobalPositioningSystemTime standard.
            if (GPSTimeRadio.Checked)
            {
                startjd = new JulianDate(StartTime.Value, TimeStandard.GlobalPositioningSystemTime);
                stopjd  = new JulianDate(StopTime.Value, TimeStandard.GlobalPositioningSystemTime);
            }
            else
            {
                // otherwise, the default time standard is UTC
                startjd = new JulianDate(StartTime.Value);
                stopjd  = new JulianDate(StopTime.Value);
            }

            // Now we''ll create the variables we'll need for iterating through time.
            // The propagator requires a Duration type be used to specify the timestep.
            Duration dur      = stopjd - startjd;
            double   timestep = Double.Parse(TimeStep.Text);

            // Initialize the progressbar with appropriate values
            progressBar1.Maximum = (int)dur.TotalSeconds;
            progressBar1.Step    = (int)timestep;

            // now we'll iterate through time by adding seconds to the start time JulianDate
            // creating a new JulianDate 'evaluateTime' each time step.
            for (double t = 0; t <= dur.TotalSeconds; t += timestep)
            {
                JulianDate evaluateTime = startjd.AddSeconds(t);

                // The string 'trackedSVs' is the start of a string we'll continue to build through this time
                // iteration. It will contain the info we'll need to put in the DOP graph tooltips for the different
                // DOP series (VDOP, HDOP, etc.)
                String trackedSVs = Localization.Tracked + ": ";

                // The evaluator method GetTrackedSatellites will take the current time and the initial list of satellites and
                // determine which satellites can be tracked based on the receiver constraints setup earlier. This method
                // returns a PlatformCollection object as well (though we'll cast each member of the Collection to a GPSSatellite type)
                int[] trackedSatellites = satTrackingEvaluator.Evaluate(evaluateTime);

                foreach (int satelliteIndex in trackedSatellites)
                {
                    Platform satellite = receiver.NavigationSatellites[satelliteIndex];

                    // Now we have access to a Platform object representing a GPS satellite and calculate the azimuth and elevation
                    // of each.  Note that we're just calculating the azimuth and elevation, but could just as easily get the
                    // range as well.
                    AzimuthElevationRange aer = aerEvaluators[satelliteIndex].Evaluate(evaluateTime);

                    // Get the GpsSatelliteExtension attached to the platform. The extension extends a
                    // platform with GPS-specific information. In this case, we need the
                    // satellites PRN.
                    GpsSatelliteExtension extension = satellite.Extensions.GetByType <GpsSatelliteExtension>();

                    // Create two separate PointPairLists to hold the data stored by Time and Azimuth
                    PointPairList thisTimePointList, thisAzPointList;

                    // Before we can arbitrarily create new PointPair Lists, we have to see if the Data Storage structures already contain a list
                    // for this PRN.
                    // The variables AzElData_TimeBased and AzElData_AzimuthBased are dictionaries that hold the PointPairLists using the PRN
                    // as a key. We use this structure to store a large amount of data for every satellite in a single, easy to access, variable.
                    // if the satellite we're currently looking at already has a list defined in the dictionary, we'll use that one, otherwise
                    // we'll create a new list
                    if (AzElData_TimeBased.ContainsKey(extension.PseudoRandomNumber))
                    {
                        thisTimePointList = AzElData_TimeBased[extension.PseudoRandomNumber];
                        AzElData_TimeBased.Remove(extension.PseudoRandomNumber);
                    }
                    else
                    {
                        thisTimePointList = new PointPairList();
                    }

                    if (AzElData_AzimuthBased.ContainsKey(extension.PseudoRandomNumber))
                    {
                        thisAzPointList = AzElData_AzimuthBased[extension.PseudoRandomNumber];
                        AzElData_AzimuthBased.Remove(extension.PseudoRandomNumber);
                    }
                    else
                    {
                        thisAzPointList = new PointPairList();
                    }

                    // Now to get the actual Azimuth and elevation data

                    // Converting your Radians to degrees here makes the data appear in a more readable format. We also constrain the azimuth
                    // to be within the interval [0, 2*pi]
                    double azimuth   = Trig.RadiansToDegrees(Trig.ZeroToTwoPi(aer.Azimuth));
                    double elevation = Trig.RadiansToDegrees(aer.Elevation);
                    #endregion

                    // now create the point for the Azimuth based data
                    PointPair thisAzPoint = new PointPair(azimuth, elevation);
                    // and add the tooltip (ZedGraph uses the Tag property on a PointPair for the tooltip on that datapoint )
                    thisAzPoint.Tag = String.Format("PRN {0}, {1}, " + Localization.Az + ": {2:0.000}, " + Localization.El + ": {3:0.000}", extension.PseudoRandomNumber, evaluateTime.ToDateTime().ToString("M/d/yyyy, h:mm tt"), azimuth, elevation);
                    // and finally add this point to the list
                    thisAzPointList.Add(thisAzPoint);

                    // now we'll do the same for the time-based data, instead of adding the Az and El, we'll add the time and El.
                    // Create a new XDate object to store the time for this point
                    double txd = (double)new XDate(evaluateTime.ToDateTime());
                    // add the time and elevation data to this point
                    PointPair thisTimePoint = new PointPair(txd, Trig.RadiansToDegrees(aer.Elevation));
                    // Create the tooltip tag
                    thisTimePoint.Tag = String.Format("PRN {0}, {1}, " + Localization.Az + ": {2:0.000}, " + Localization.El + ": {3:0.000}", extension.PseudoRandomNumber, evaluateTime.ToDateTime().ToString("M/d/yyyy, h:mm tt"), azimuth, elevation);
                    // finally add this point to the list
                    thisTimePointList.Add(thisTimePoint);

                    //Now that this data is all calculated, we'll add the point lists to the correct data structures for the GpsSatellite we're working with
                    AzElData_TimeBased.Add(extension.PseudoRandomNumber, thisTimePointList);
                    AzElData_AzimuthBased.Add(extension.PseudoRandomNumber, thisAzPointList);

                    // now update the 'trackedSVs' string to be used for the DOP data tooltip
                    // wee need to do this inside the GpsSatellite loop because we need to get the entire list of tracked SVs for this time step.
                    // we won't use this string until we're out of the loop however.
                    trackedSVs += extension.PseudoRandomNumber.ToString() + ", ";
                }

                // now we're out of the GpsSatellite loop, we'll do some string manipulation to get the tooltip for the DOP data.
                // (gets rid of the last inserted comma)
                string svs = trackedSVs.Substring(0, trackedSVs.LastIndexOf(' ') - 1);
                try
                {
                    // Now we use the evaluator to calculate the DilutionOfPrecision for us for this timestep
                    DilutionOfPrecision dop = dopEvaluator.Evaluate(evaluateTime);

                    // if the dop object throws an exception, there aren't enough tracked satellites to form a navigation solution (typically < 4 tracked)
                    // in that case we leave the data for this time unfilled. The graph will then have empty spots for this time.

                    // Here we create a new PointPair and a new XDate to add to the X-Axis
                    PointPair pp;
                    double    txd = (double)new XDate(evaluateTime.ToDateTime());
                    // add the East DOP value and the time to the PointPair and set the tooltip tag property for this series.
                    pp     = new PointPair(txd, dop.X);
                    pp.Tag = String.Format("{0}\n{1} " + Localization.EDOP + ": {2:0.000}", svs, evaluateTime.ToDateTime().ToString("M/d/yyyy, h:mm tt"), dop.X);
                    // add the point to the 0th element of the DOPData structure
                    DOPData[0].Add(pp);
                    // repeat for North DOP
                    pp     = new PointPair(txd, dop.Y);
                    pp.Tag = String.Format("{0}\n{1} " + Localization.NDOP + ": {2:0.000}", svs, evaluateTime.ToDateTime().ToString("M/d/yyyy, h:mm tt"), dop.Y);
                    DOPData[1].Add(pp);
                    // repeat for the Vertical DOP
                    pp     = new PointPair(txd, dop.Z);
                    pp.Tag = String.Format("{0}\n{1} " + Localization.VDOP + ": {2:0.000}", svs, evaluateTime.ToDateTime().ToString("M/d/yyyy, h:mm tt"), dop.Z);
                    DOPData[2].Add(pp);
                    // repeat for the Horizontal DOP
                    pp     = new PointPair(txd, dop.XY);
                    pp.Tag = String.Format("{0}\n{1} " + Localization.HDOP + ": {2:0.000}", svs, evaluateTime.ToDateTime().ToString("M/d/yyyy, h:mm tt"), dop.XY);
                    DOPData[3].Add(pp);
                    // repeat for the Position DOP
                    pp     = new PointPair(txd, dop.Position);
                    pp.Tag = String.Format("{0}\n{1} " + Localization.PDOP + ": {2:0.000}", svs, evaluateTime.ToDateTime().ToString("M/d/yyyy, h:mm tt"), dop.Position);
                    DOPData[4].Add(pp);
                    // repeat for the Time DOP
                    pp     = new PointPair(txd, dop.Time);
                    pp.Tag = String.Format("{0}\n{1} " + Localization.TDOP + ": {2:0.000}", svs, evaluateTime.ToDateTime().ToString("M/d/yyyy, h:mm tt"), dop.Time);
                    DOPData[5].Add(pp);
                    // repeat for the Geometric DOP
                    pp     = new PointPair(txd, dop.Geometric);
                    pp.Tag = String.Format("{0}\n{1} " + Localization.GDOP + ": {2:0.000}", svs, evaluateTime.ToDateTime().ToString("M/d/yyyy, h:mm tt"), dop.Geometric);
                    DOPData[6].Add(pp);

                    // Notice here that the different DOP values (East, North, etc) were denoted by the dop.X, dop.Y etc. This is because the
                    // DOP values could be in any coordinate system. In our case, we're in the ENU coordinate system an X represents East, Y
                    // represents North, Z represents Vertical, XY represents horizontal. You can change the reference frame the DOP is reported in
                    // but you will then have to understand that the dop.X value corresponds to your X-defined axis and so on.
                }
                catch
                {
                    // Do Nothing here - we just won't add the data to the data list
                }
                // update the progress bar - we're done with this time step!
                progressBar1.PerformStep();
            }
            // finally update the graphs
            UpdateDopGraph();
            updateAzElGraph();

            // reset the progress bar
            progressBar1.Value = 0;

            // and set the appropriate button states
            SetControlStates(true);
        }
Пример #3
0
        /// <summary>
        /// Construct a default instance.
        /// </summary>
        public Main()
        {
            InitializeComponent();

            m_insight3D      = new Insight3D();
            m_insight3D.Dock = DockStyle.Fill;
            m_insight3DPanel.Controls.Add(m_insight3D);

            m_defaultStart = GregorianDate.Now;
            m_defaultEnd   = m_defaultStart.AddDays(1);

            m_animation            = new SimulationAnimation();
            SceneManager.Animation = m_animation;

            m_display = new ServiceProviderDisplay();

            m_forceModelSettings = new ForceModelSettings(s_jplData, GetDataFilePath("EarthGravityFile_EGM2008.grv"));
            m_integratorSettings = new IntegratorSettings();
            m_area.Text          = "20";
            m_mass.Text          = "500";

            // Create overlay toolbar and panels
            m_overlayToolbar = new OverlayToolbar(m_insight3D);
            m_overlayToolbar.Overlay.Origin = ScreenOverlayOrigin.BottomCenter;

            // Initialize the text panel
            TextureScreenOverlay textPanel = new TextureScreenOverlay(0, 0, 220, 35)
            {
                Origin             = ScreenOverlayOrigin.TopRight,
                BorderSize         = 0,
                BorderColor        = Color.Transparent,
                BorderTranslucency = 1.0f,
                Color        = Color.Transparent,
                Translucency = 1.0f
            };

            SceneManager.ScreenOverlays.Add(textPanel);

            m_dateTimeFont = new Font("Courier New", 12, FontStyle.Bold);
            Size textSize = Insight3DHelper.MeasureString(m_defaultStart.ToString(), m_dateTimeFont);

            m_textOverlay = new TextureScreenOverlay(0, 0, textSize.Width, textSize.Height)
            {
                Origin     = ScreenOverlayOrigin.Center,
                BorderSize = 0
            };
            textPanel.Overlays.Add(m_textOverlay);

            // Show label for the moon
            m_insight3D.Scene.CentralBodies[CentralBodiesFacet.GetFromContext().Moon].ShowLabel = true;

            // Set the name for the element that will get propagated
            m_elementID = "Satellite";

            // Subscribe to the time changed event
            SceneManager.TimeChanged += OnTimeChanged;

            // Set the start and stop times
            m_start.CustomFormat = DateFormat;
            m_end.CustomFormat   = DateFormat;

            m_start.Text = m_defaultStart.ToString(DateFormat);
            m_end.Text   = m_defaultEnd.ToString(DateFormat);

            m_animation.Time      = m_defaultStart.ToJulianDate();
            m_animation.StartTime = m_defaultStart.ToJulianDate();
            m_animation.EndTime   = m_defaultEnd.ToJulianDate();

            // Dynamically set the camera's position and direction so that the camera will always be pointed at the daylit portion of the earth.
            EarthCentralBody       earth               = CentralBodiesFacet.GetFromContext().Earth;
            SunCentralBody         sun                 = CentralBodiesFacet.GetFromContext().Sun;
            VectorTrueDisplacement earthToSunVector    = new VectorTrueDisplacement(earth.CenterOfMassPoint, sun.CenterOfMassPoint);
            VectorEvaluator        earthToSunEvaluator = earthToSunVector.GetEvaluator();
            Cartesian     earthToSunCartesian          = earthToSunEvaluator.Evaluate(new JulianDate(m_defaultStart));
            UnitCartesian earthToSunUnitCartesian      = new UnitCartesian(earthToSunCartesian);
            UnitCartesian cameraDirection              = new UnitCartesian(earthToSunUnitCartesian.Invert());
            Cartesian     cameraPosition               = new Cartesian(earthToSunUnitCartesian.X * 50000000, earthToSunUnitCartesian.Y * 50000000, earthToSunUnitCartesian.Z * 50000000);

            m_insight3D.Scene.Camera.Position  = cameraPosition;
            m_insight3D.Scene.Camera.Direction = cameraDirection;
        }
Пример #4
0
        /// <summary>
        /// Create a Platform for the requested satellite using a TLE for position.
        /// The satellite will be visually represented by a labeled glTF model,
        /// the satellite's orbit will be shown, and vectors will be drawn for
        /// the body axes of the satellite, plus a vector indicating the direction
        /// of the sun.
        /// </summary>
        private void CreateSatellite()
        {
            // Get the current TLE for the given satellite identifier.
            var tleList = TwoLineElementSetHelper.GetTles(m_satelliteIdentifier, JulianDate.Now);

            // Use the epoch of the first TLE, since the TLE may have been loaded from offline data.
            m_epoch = tleList[0].Epoch;

            // Propagate the TLE and use that as the satellite's location point.
            var locationPoint = new Sgp4Propagator(tleList).CreatePoint();

            m_satellite = new Platform
            {
                Name          = "Satellite " + m_satelliteIdentifier,
                LocationPoint = locationPoint,
                // Orient the satellite using Vehicle Velocity Local Horizontal (VVLH) axes.
                OrientationAxes = new AxesVehicleVelocityLocalHorizontal(m_earth.FixedFrame, locationPoint),
            };

            // Set the identifier for the satellite in the CZML document.
            m_satellite.Extensions.Add(new IdentifierExtension(m_satelliteIdentifier));

            // Configure a glTF model for the satellite.
            m_satellite.Extensions.Add(new ModelGraphicsExtension(new ModelGraphics
            {
                // Link to a binary glTF file.
                Model = new CesiumResource(GetModelUri("satellite.glb"), CesiumResourceBehavior.LinkTo),
                // By default, Cesium plays all animations in the model simultaneously, which is not desirable.
                RunAnimations = false,
            }));

            // Configure a label for the satellite.
            m_satellite.Extensions.Add(new LabelGraphicsExtension(new LabelGraphics
            {
                // Use the name of the satellite as the text of the label.
                Text = m_satellite.Name,
                // Change the color of the label after 12 hours. This demonstrates specifying that
                // a value varies over time using intervals.
                FillColor = new TimeIntervalCollection <Color>
                {
                    // Green for the first half day...
                    new TimeInterval <Color>(JulianDate.MinValue, m_epoch.AddDays(0.5), Color.Green, true, false),
                    // Red thereafter.
                    new TimeInterval <Color>(m_epoch.AddDays(0.5), JulianDate.MaxValue, Color.Red, false, true),
                },
                // Only show label when camera is far enough from the satellite,
                // to avoid visually clashing with the model.
                DistanceDisplayCondition = new Bounds(1000.0, double.MaxValue),
            }));

            // Configure graphical display of the orbital path of the satellite.
            m_satellite.Extensions.Add(new PathGraphicsExtension(new PathGraphics
            {
                // Configure the visual appearance of the line.
                Material = new PolylineOutlineMaterialGraphics
                {
                    Color        = Color.White,
                    OutlineWidth = 1.0,
                    OutlineColor = Color.Black,
                },
                Width = 2.0,
                // Lead and Trail time indicate how much of the path to render.
                LeadTime  = Duration.FromMinutes(44.0).TotalSeconds,
                TrailTime = Duration.FromMinutes(44.0).TotalSeconds,
            }));

            // Create vectors for the X, Y, and Z axes of the satellite.
            m_satelliteXAxis = CreateAxesVector(m_satellite, CartesianElement.X, Color.Green, "SatelliteX");
            m_satelliteYAxis = CreateAxesVector(m_satellite, CartesianElement.Y, Color.Red, "SatelliteY");
            m_satelliteZAxis = CreateAxesVector(m_satellite, CartesianElement.Z, Color.Blue, "SatelliteZ");

            // Create a vector from the satellite to the Sun.

            // Compute the vector from the satellite's location to the Sun's center of mass.
            var sunCenterOfMassPoint = CentralBodiesFacet.GetFromContext().Sun.CenterOfMassPoint;
            var vectorSatelliteToSun = new VectorTrueDisplacement(m_satellite.LocationPoint, sunCenterOfMassPoint);

            // Create the visual vector.
            m_satelliteSunVector = new GraphicalVector
            {
                LocationPoint  = m_satellite.LocationPoint,
                Vector         = vectorSatelliteToSun,
                VectorGraphics = new VectorGraphics
                {
                    Length = 5.0,
                    Color  = Color.Yellow,
                },
            };

            // Set the identifier for the vector in the CZML document.
            m_satelliteSunVector.Extensions.Add(new IdentifierExtension("SunVector"));

            // Orient the solar panels on the satellite model to point at the sun.
            var satelliteYVector = m_satellite.OrientationAxes.GetVectorElement(CartesianElement.Y);

            // allow only Z axis to rotate to follow sun vector. Constrain sun vector to Y, and satellite Y vector to X.
            var constrainedAxes = new AxesAlignedConstrained(satelliteYVector, AxisIndicator.First, vectorSatelliteToSun, AxisIndicator.Second);

            // Satellite axes are Vehicle Velocity Local Horizontal (VVLH) axes, where X is forward and Z is down,
            // but Cesium model axes are Z forward, Y up. So, create an axes rotates to the Cesium model axes.
            var offset = new UnitQuaternion(new ElementaryRotation(AxisIndicator.First, -Math.PI / 2)) *
                         new UnitQuaternion(new ElementaryRotation(AxisIndicator.Third, Math.PI / 2));
            var cesiumModelAxes = new AxesFixedOffset(m_satellite.OrientationAxes, offset);

            // The rotation will be from the Cesium model axes to the constrained axes.
            var solarPanelRotationAxes = new AxesInAxes(constrainedAxes, cesiumModelAxes);

            // Add a node transformation to rotate the SolarPanels node of the model.
            m_satellite.Extensions.GetByType <ModelGraphicsExtension>().ModelGraphics.NodeTransformations = new Dictionary <string, NodeTransformationGraphics>
            {
                {
                    "SolarPanels", new NodeTransformationGraphics
                    {
                        Rotation = new AxesCesiumProperty(solarPanelRotationAxes)
                    }
                }
            };
        }