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(); }
/// <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); }
/// <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; }
/// <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) } } }; }