Satellite: Difference between revisions

From Maria GDK Wiki
Jump to navigation Jump to search
()
(Added section with example for FootprintSettings property)
 
(46 intermediate revisions by 3 users not shown)
Line 119: Line 119:
==== ISatelliteFootprint class ====
==== ISatelliteFootprint class ====
Defines a footprint. The properties ViewAngle, TiltAngle and RotateAngle defines the size and position of the footprint.  These properties are used to calculate the  
Defines a footprint. The properties ViewAngle, TiltAngle and RotateAngle defines the size and position of the footprint.  These properties are used to calculate the  
dimensions of the footprint with the Satellite.CalculateFootprintDimensions method. Actual geo-points for the footprint is then calculated with SatelliteFootprintRenderer.CreateFootprintPoints.
dimensions of the footprint with the Satellite.CalculateFootprintDimensions method. Actual geo-points for the footprint is then calculated with SatelliteFootprintRenderer.CreateFootprintPoints.  
 
===== FootprintSettings property =====
Use property FootprintSettings to define how the footprint is rendered. The individual settings can be added/subtracted using logical operators. See code example below.<syntaxhighlight lang="c#">
public enum FootprintSettings
    {
        /// <summary>
        /// No hub lines are rendered
        /// </summary>
        None = 0x1,
        /// <summary>
        /// The north hub line is rendered.
        /// </summary>
        Forward = 0x2,
        /// <summary>
        /// The east hub line is rendered.
        /// </summary>
        Right = 0x4,
        /// <summary>
        /// The south hub line is rendered.
        /// </summary>
        Back = 0x8,
        /// <summary>
        /// The west hub line is rendered.
        /// </summary>
        Left = 0x10,
        /// <summary>
        /// The hub lines are rendered geographical correct.
        /// </summary>
        GeoCorrect = 0x20,
    }
 
// Example use for drawing all hub lines.
footprint.FootprintSettings = FootprintSettings.Forward | FootprintSettings.Right | FootprintSettings.Back | FootprintSettings.Left;
</syntaxhighlight>


=== ISatelliteLineOfSight class ===
=== ISatelliteLineOfSight class ===
Line 125: Line 159:


=== ISatelliteTimeTicks class ===
=== ISatelliteTimeTicks class ===
This add time ticks to the trajectory rendering. It is implemented by the Satellite class so that these settings can be handled before an actual trajectory is added to the satellite object.
This add time ticks to the trajectory rendering. It is implemented by the Satellite class so that these settings can be handled before an actual trajectory is added to the satellite object. It is also implemented by the SatelliteLayerViewModel class, ticks values set on the viewmodel will be used on all satellites which have no set tick values.


== How to create satellite objects ==
== How to create satellite objects ==
Line 196: Line 230:
This class updates the satellite system time.
This class updates the satellite system time.


=== ISatellitesUpdate ===
=== SatellitesStore ===
This interface must be implemented by the viewmodel so that the SatelliteSystemTimeUpdate object can update the satellite positions.
This class handles the satellite system time and automatically updating the positions.
 
=== Code example for automatic satellite position updating ===
This code shows the major details of how the SatelliteLayerViewModel handles the automatic updating of satellite positions.
 
<source lang="c#">
public partial class SatelliteLayerViewModel : GeoLayerViewModel, ISatelliteLayerViewModel, ISatellitesUpdate, .....
{
  private readonly SatelliteSystemTimeUpdate _satellitesSystemTimeUpdate;
 
  public SatelliteLayerViewModel(.....)
  {
      ...
      //this object handles the satellite system time; either actual time or starting from a given base time for example yesterday 13.00. In addition the time updating can be increased or decreased,
      //or even reversed using the playback property.
      _satellitesSystemTimeUpdate = new SatelliteSystemTimeUpdate(this);
  }
    //The satellite system time is available externally, but is actual implemented by the SatelliteSystemTimeUpdate class.
    public DateTime Time
    {
      get => _satellitesSystemTimeUpdate.Time;
      set => _satellitesSystemTimeUpdate.Time = value;
    }
 
    //If null, the system time will be current time (DateTime.Now). If set, the given time is the start time when the automatic update is turned on.
    public DateTime? SatelliteBaseTime
    {
      get => _satellitesSystemTimeUpdate.SatelliteBaseTime;
      set => _satellitesSystemTimeUpdate.SatelliteBaseTime = value;
    }
 
    //If 1.0, the automatic time update is real time.
    //If different than 1.0, the time update is equal to the playback factor.
    //if negative, the time update is reversed meaning that the satellites are moving backward in time.
    public double PlaybackSpeed
    {
      get => _satellitesSystemTimeUpdate.PlaybackSpeed;
      set => _satellitesSystemTimeUpdate.PlaybackSpeed = value;
    }
 
    //starts the automatic updating of the satellite system time.
    public bool StartAutomaticSatellitePositioning(double updateFrequency)
    {
      updateFrequency = updateFrequency < 0.3 ? 0.3 : updateFrequency;
 
      _satellitesSystemTimeUpdate.SetTimeNow();
      _satellitesSystemTimeUpdate.SetAndStartTimerTime(updateFrequency * 1000.0, SatelliteLayerSettings, GeoContext);
      IsAutomaticPositioning = true;
      return true;
    }
 
    //stops the automatic updating of system time
    public bool StopAutomaticSatellitePositioning()
    {
      _satellitesSystemTimeUpdate.Stop();
      IsAutomaticPositioning = false;
      return true;
    }
 
    //This method is called by _satellitesSystemTimeUpdate on each system time update
    public void UpdateSatelliteTimes(bool symbol, bool trajectory)
    {
      UpdateSatellites(symbol, trajectory);
    }
 
    //do the actual update of all satellites.
    //This code only shows the necessary code for updating the satellite positions.                                                                      
    private void UpdateSatellites(bool symbol, bool trajectory)
    {
      //make sure that this is the only update instance
      lock (_lockUpdateSatellite)
      {
          //prevent any rendering while updating
          LockRedraw = true;
 
          var satellites = GetSatellites();
          foreach (var satellite in satellites.Values.OfType<Satellite>())
          {
            if (symbol)
                satellite.UpdateSatellite(Time);
 
            if (!trajectory) continue;
                 
            satellite.UpdateSatellite(Time,
                        satellite.TrajectoryBackwardSpan.Ticks > 0
                            ? satellite.TrajectoryBackwardSpan
                            : TrajectoryBackwardSpan,
                        satellite.TrajectoryForwardSpan.Ticks > 0
                            ? satellite.TrajectoryForwardSpan
                            : TrajectoryForwardSpan);
          }
 
          LockRedraw = false;
      }
    }
 
    //GeoLayerViewModel override
    public override void Update()
    {
        ...
 
        //Update timer frequency based on context scale. For performance, the frequency is increased with lower context scale since fewer satellites then will visible. The positions for all satellites
        //will be updated since this can be processed without increasing the CPU, but only visible satellites are actually rendered. Rendering many satellites will affect the CPU.
        if (ViewportExtentChanged && IsAutomaticPositioning)
          _satellitesSystemTimeUpdate.SetAndStartTimerTime(SatelliteLayerSettings, GeoContext);
 
        ...
    }
}
</source>


== Historic satellites ==
== Historic satellites ==
A satellite's TLE is updated over time since the trajectory changes slightly for each turn. It might therefore be of interest to render the different trajectories and positions for a satellite.The historic instances of the satellite will all have the same satellite ID so this property can not be used to differentiate between the current satellite object and the historic satellite objects.
A satellite's TLE is updated over time since the trajectory changes slightly for each turn. It might therefore be of interest to render the different trajectories and positions for a satellite.The historic instances of the satellite will all have the same satellite ID so this property can not be used directly to differentiate between the current satellite object and the historic satellite objects. Instead the historic SatelliteId is set to the original satellite's ID plus an historic offset and an index number equal to number of historic satellites.  


=== ISatelliteHistory class ===
=== ISatelliteHistory class ===
Line 337: Line 261:
         /// </summary>
         /// </summary>
         new bool IsSatelliteClass { get; }
         new bool IsSatelliteClass { get; }
        /// <summary>
        /// Returns the non-historic SatelliteId
        /// </summary>
        int RegularSatelliteId { get; }
     }
     }
</source>
</source>


The IsSatelliteClass will be false for historic satellites and true for regular satellite. For an historic satellite SatelliteId = RegularSatelliteId + HistoryOffset + HistoryIndex.
The IsSatelliteClass will be false for historic satellites and true for regular satellite. For an historic satellite SatelliteId = RegularSatelliteId + HistoryOffset + HistoryIndex. This value can easily be retrieved by RegularSatelliteId.


=== Create a historic satellite object ===
=== Create a historic satellite object ===
Line 360: Line 289:
</source>
</source>


This test code adds a historic satellite to the viewmodel:
This test code is more realistic since it adds the historic satellite to the viewmodel:
<source lang="c#">
<source lang="c#">
     var satellite = new GeoFramework.SatelliteLayer.Satellite();             
     var satellite = new GeoFramework.SatelliteLayer.Satellite();             
Line 386: Line 315:


=== Observation points in the satellite layer ===  
=== Observation points in the satellite layer ===  
The ISatelliteLineOfSight adds line-of-sight functionality like adding and removing points to the satellite layer.
The ISatelliteLineOfSight adds line-of-sight functionality like adding and removing observation points to the satellite layer.


This code adds an observation point to the satellite layer. The point will track the given satellite.
This code adds an observation point to the satellite layer. The point will track the given satellite.
Line 414: Line 343:
==== SatelliteInSight ====
==== SatelliteInSight ====
Checks if a specific satellite is tracked by any line-of-sight points or if a specific line-of-sight point tracks a specific satellite.
Checks if a specific satellite is tracked by any line-of-sight points or if a specific line-of-sight point tracks a specific satellite.


=== Defining an observation point ===
=== Defining an observation point ===
Line 605: Line 533:


</source>
</source>
=== Check if observation points are in any satellite's footprint ===
The method ISatelliteLineOfSight.ComputeInFootprints starts the asynchronous process of checking if an observation point is within any footprints.
This test code shows how to start the footprint process
<source lang="c#">
  ....
 
  viewmodel.ComputeInFootprints(true);
 
  ...
  Assert.IsTrue(viewmodel.HasInsideFootprintComputations);
  var results = viewmodel.LineOfSightViewPoint(id);
  var status = results.Status.First();
  var computations = status.Value.FootprintComputations.Computations;
 
    ....
</source>
=== When are satellites inside footprints ===
The method ISatelliteLineOfSight.ComputeWhenInFootprints starts the asynchronous process of checking when the satellite is inside a footprint.
This test code shows how to start the footprint process
<source lang="c#">
  ....
 
  viewmodel.ComputeWhenInFootprints(startTime, stopTime, steptTime, false);
 
  ...
  Assert.IsTrue(viewmodel.HasInsideFootprintComputations);
  var results = viewmodel.LineOfSightViewPoint(id);
  var status = results.Status.First();
  var computations = status.Value.FootprintComputations.Computations;
 
    ....
</source>
=== Analyze results as XML ===
The method ISatelliteLineOfSight.StatusAsXml returns the analyze results as XML. The XML content is dependent of type of analyze process.
This test code shows how to get the results as XML
<source lang="c#">
  ....
  viewmodel.ComputeInSight(true);
  ...
  var xml = viewmodel.StatusAsXml();
  ....
</source>
EXample of analyze process as XML:
[[File:Xml analyze.png|frameless|XML]]
== Implement satellite functionality in GDK layer ==
The satellites can easily be integrated in a GDK layer since the handling of the satellites are extracted into a dedicated classes. Automatic updating of all satellites is also extracted from the GDK layer.
=== Handling satellite objects ===
The satellites are handled by a dedicated store object that can be shared between several layers, for example 2D and 3D.
==== The satellite store object ====
The store object can either be created by the viewmodel or created separately and then passed into the viewmodel through the constructor. '''SatellitesStore''' implements the following interfaces: ISatellitesStore, ISatelliteHistories and ISatelliteListeners.
==== Satellite system time ====
The system time is extracted into a dedicated class, '''SatelliteSystemTimeUpdate''', which is used by the store object.
=== Connecting viewmodel and satellite store ===
Automatical update of satellite time and thereby positions are handled by SatellitesStore and SatelliteSystemTimeUpdate, but the viewmodel might need to be informed about the position changes.
==== The listener class ====
The listener class, '''ISatelliteListeners''', handles which objects that shall be informed about position changes.
==== Position changes information ====
The class, '''ISatellitesUpdate''', is used to relay information about position changes.
=== Connecting everything ===
The following extracted code shows how everything is connected in the SatelliteLayerViewModel.
<source lang="c#">
  public partial class SatelliteLayerViewModel : ... ISatelliteLayerViewModel, ISatellitesUpdate, ...
  {
      private readonly SatellitesStore _satellitesStore;
      //constructor without store object, layer uses its own store object
      public SatelliteLayerViewModel(IGeoUnitsSetting geoUnitsSetting, IGeoContext geoContext, IGeoNavigator geoNavigator, SatelliteLayerViewFactory satelliteObjectLayerViewFactory) ....
      {
          ...
          _satellitesStore = new SatellitesStore();
          _satellitesStore.AddListener(this);
      }
      //store object is already created, use this object
      public SatelliteLayerViewModel(ISatellitesStore satelliteStore, IGeoUnitsSetting geoUnitsSetting, ....
      {
          ...
          _satellitesStore = (SatellitesStore) satelliteStore;
          _satellitesStore.AddListener(this);
      }
      //starts automatic updating
      public bool StartAutomaticSatellitePositioning(double updateFrequency)
      {
          updateFrequency = updateFrequency < 0.3 ? 0.3 : updateFrequency;
          _satellitesStore.SatelliteSystemTimeUpdate.ResetAutomaticTimerBaseTime();
          _satellitesStore.SatelliteSystemTimeUpdate.SetAndStartTimerTime(updateFrequency * 1000.0, SatelliteLayerSettings, GeoContext);
          IsAutomaticPositioning = true;
      }
      //stop updating
      public bool StopAutomaticSatellitePositioning()
      {
          _satellitesStore.SatelliteSystemTimeUpdate.Stop(); 
          IsAutomaticPositioning = false;     
      }
      public override void Update()
      {
          ...
          //must update frequency if scale is changed
          if (ViewportExtentChanged && IsAutomaticPositioning )
          {
              _satellitesStore.SatelliteSystemTimeUpdate.SetAndStartTimerTime(SatelliteLayerSettings, GeoContext);
          }
          ...
      }
     
      //ISatellitesUpdate implementations
      //callback before satellites are automatically updated
      public void BeforeSatellitesUpdate()
      {
          LockRedraw = true;
      }
      //callback after satellites are automatically updated
      public void AfterSatellitesUpdate()
      {
          LockRedraw = false;
          var trackedSatellite = _satellitesStore.TrackedSatellite(TrackSatellite);
          if (trackedSatellite != null)
              CenterToPosition(trackedSatellite as Satellite);
      }
  }
</source>
== Satellites in 3D ==
Satellites can also be displayed in 3D. This is handled by the class '''TPG.Globe3D.SatelliteLayer'''. When using 2D and 3D in the same application you should make a SatellitesStore outside the layers and inject it in both 2D and 3D layers.
=== Initializing the 3D satellite layer ===
The following code example sets up both 2D and 3D satellite layers from the same satellite store.
<source lang="c#">
// Common satellites store
_satellites = new SatellitesStore();
// 2D layer
var satelliteLayerViewFactory = new SatelliteLayerViewFactory(_drawObjectLayerFactory.SymbolsStore);
_satelliteLayerViewModel = new SatelliteLayerViewModel(_satellites, _geoUnitsSetting, geoContext, geoNavigator, satelliteLayerViewFactory);
_mapViewModel.Layers.Add(_satelliteLayerViewModel);
// 3D layer
_satelliteLayer3D = new SatelliteLayer(_map3DViewModel, _satellites, drawObjectDataFactory, stylingXml);
</source>
=== Adding and updating satellites ===
The 3D layer will display all visible '''ISatellite''' objects in the '''SatellitesStore''' with the same styling parameters as are used in 2D. The 3D graphics will be updated upon each ''UpdateSatellites()'' call in the '''SatellitesStore''', so if you for example control this through an '''ISatelliteSystemTimeUpdate''' interface, the satellites will be continuously updated.
If you change any of the styling or other visible parameters of the satellites, you may need to update explicitly, either through a call to ''SatellitesStore.UpdateSatellites(true)'' or by calling ''SatelliteLayer.UpdateSatellites()'' directly.
=== Interacting with satellites in 3D ===
The satellite icons are 3D items of the same type as tracks and point symbols, and you may use the ''IGlobe3DViewModel.ClickedItem'' event to respond to mouse clicks on these. Each satellite item will have an '''ItemId''' consisting of the '''SatelliteLayer''' point list id (accessible through ''ISatelliteLayer.PointListId'') and the satellite ID as ''InstanceID''.
The following example illustrates how you can toggle the 3D label on a clicked satellite:
<source lang="c#">
_map3DViewModel.ClickedItemEvent += clickedSatellite;
private void clickedSatellite(object sender, ClickedItemEventArgs args)
{
    // Only react on right-click
    if (args.MouseButtonEventArgs.ChangedButton != MouseButton.Right)
        return;
    var itemId = args.ItemId;
    if (itemId.ListId == _satelliteLayer3D.PointListId)
    {
        var satId = int.Parse(itemId.InstanceId);
        var sat = _satellites.GetSatellite(satId);
        sat.DrawLabels = !sat.DrawLabels;
        _satellites.UpdateSatellites(true);
    }
}
</source>
[[Category:Satellite]]

Latest revision as of 12:23, 7 November 2023

The GDK layer "Satellite" defined in the namespace TPG.GeoFramework.SatelliteLayer handles satellites which therefore easily can be separated from other draw types. Satellite objects in this layer are controlled through the TPG.GeoFramework.SatelliteLayer.SatelliteLayerViewModel class.

The TLE string

A satellite position and its trajectory is computed from a TLE string. The format of the TLE is described in https://www.space-track.org/documentation#/tle. The TLE for a satellite can for example be acquired from www.space-track.org using the TPG.Satellite.Implementation.SatelliteUtil.GetSpaceTrack method. The content of the TLE will be unique for each satellite and is also adjusted through time to give as accurate position computations as possible.

//Example of TLE text used in tests
public static string TestTle()
{
    var tle = "0 ISS (ZARYA)";
    tle += "\n1 25544U 98067A   20227.03014521  .00002523  00000-0  53627-4 0  9998";
    tle += "\n2 25544  51.6461  62.1977 0001435  30.4470 104.2862 15.49164713240985";
    return tle;
}

Convert time to satellite position

The mathematics to convert time to position based on the TLE is done with an external library. Currently the conversion is supported by two different libraries. The functionality is available through the facades TPG.Data.Satellite.Interface.ISatelliteData and TPG.Data.Satellite.Interface.ISatelliteTle. This is done so that adding new libraries can be done without affecting the satellite code.

The creation process for a satellite object, based on given TLE and time, is not available for the end user. This process will automatically select one of the implemented libraries, but it is possible to select a specific library:

   //create satellite without selecting library
   var satellite1 = new TPG.GeoFramework.SatelliteLayer.Satellite();
  
   //select the first implemented library. Usually this is done through UI 
   var tle = new SatelliteTle();
   var satellite2 = new TPG.GeoFramework.SatelliteLayer.Satellite(tle.Libraries.First());

Implemented TLE libraries

These libraries are free licenses.

The OneSgp4GdkTle library

https://www.nuget.org/packages/One_Sgp4 https://github.com/1manprojects/one_Sgp4

The SgpDotNeTle library

https://www.nuget.org/packages/SGP.NET/ https://github.com/parzivail/SGP.NET

Other possible libraries

The library http://www.zeptomoby.com/satellites/ might be of interest in the future. This library has a very extensive functionality, but has not been added since it has a very high license cost.

What is a satellite?

A satellite object consists of two draw objects; a satellite position object of type symbol and a satellite trajectory object of type line. Both object types are not required, but are usually used. The position shows where the actual satellite is at the given time. The trajectory shows where the satellite has been between a given start and stop time.

Useful internet resources:

https://www.space-track.org

https://in-the-sky.org/satmap_worldmap.php

https://www.n2yo.com

https://www.phiphase.com

https://www.phiphase.com/wp-content/uploads/2021/10/satellite_coverage-1.pdf

http://www.celestrak.com

https://www.researchgate.net/figure/Conceptual-Earth-model-for-calculating-Azimuth-angle-i-i14-and-Elevation-angle-i_fig3_343181019

Satellite implementation

The Satellite class implements dedicated interfaces for different satellite functionality.

public class Satellite : ISatellite, ISatelliteTime, ISatelliteDevice, ISatelliteFootprints, ISatelliteLineOfSight, ISatelliteTimeTicks
{
.
.
}

ISatellite class

Properties for basic satellite functionality like position, trajectory, satellite ID, satellite name, visibility etc. The properties for the position and the trajectory are:

ISatellite.ISatellitePosition

The satellite position is handled by the ISatellitePosition class. The property ISatellitePosition.Position contains the (Lat,Lon) position, corresponding time for the position, satellite height at the position, and also the possibility to add additional key,value information for this point.

ISatellite.ISatelliteTrajectory

The satellite trajectory is handled by the ISatelliteTrajectory class. The property ISatelliteTrajectory.Trajectory contains a list of satellite positions which is used to draw the trajectory line. The parameters StartTime and StopTime contains the start and stop time for the trajectory.

ISatelliteTime class

Properties used in time related methods for the satellite object.

ISatelliteDevice class

Properties used in device related computations for satellite object. The most important computation is adjusting the trajectory's start and stop time so that the trajectory covers the actual device extent. This class is normally not used externally.

ISatelliteFootprints class

Adds footprint functionality to a satellite. A satellite can have many footprints with different setup where each footprint has a unique ID.

Example of satellite with several footprints: Satellite with several footprints


This code adds a footprint to a satellite:

var satellite = satelliteLayerViewModel.GetSatellite(satelliteId) as Satellite;
satellite.AddFootprint(new SatelliteFootprint()
            {
                Id = Guid.NewGuid().ToString(),
                ViewAngle = Footprint,
                CircleLineColor = LineColor,
                HubLineColor = OutlineColor != Colors.Black ? OutlineColor : LineColor,
                HubLineThickness = OutlineColor != Colors.Black && TimerSecs < 50 ? TimerSecs  : new SatelliteFootprint().HubLineThickness,
                CircleLineThickness = LineWidthValue,
                FootprintSettings = FootprintSetting(),
                LabelPlacement = FootprintSetting(),
                TiltAngle = FootprintTiltAngle,
                RotateAngle = FootprintRotateAngle,
                DrawFootprintAsStripe = FootprintAsStripe,
                AccumulateFootprintStripe = AccumulateFootprintStripe,
                StripeFillColor = stripeFillColor,
                StripeLineColor = LineColor,
                StripeFillStyle = FillStyle.Solid

            });

In addition the class contains methods to handle footprints like retrieve and remove footprints.

ISatelliteFootprint class

Defines a footprint. The properties ViewAngle, TiltAngle and RotateAngle defines the size and position of the footprint. These properties are used to calculate the dimensions of the footprint with the Satellite.CalculateFootprintDimensions method. Actual geo-points for the footprint is then calculated with SatelliteFootprintRenderer.CreateFootprintPoints.

FootprintSettings property

Use property FootprintSettings to define how the footprint is rendered. The individual settings can be added/subtracted using logical operators. See code example below.

public enum FootprintSettings
    {
        /// <summary>
        /// No hub lines are rendered
        /// </summary>
        None = 0x1,
        /// <summary>
        /// The north hub line is rendered.
        /// </summary>
        Forward = 0x2,
        /// <summary>
        /// The east hub line is rendered.
        /// </summary>
        Right = 0x4,
        /// <summary>
        /// The south hub line is rendered.
        /// </summary>
        Back = 0x8,
        /// <summary>
        /// The west hub line is rendered.
        /// </summary>
        Left = 0x10,
        /// <summary>
        /// The hub lines are rendered geographical correct.
        /// </summary>
        GeoCorrect = 0x20,
    }

// Example use for drawing all hub lines.
footprint.FootprintSettings = FootprintSettings.Forward | FootprintSettings.Right | FootprintSettings.Back | FootprintSettings.Left;

ISatelliteLineOfSight class

This class has functionality to check if a satellite is visible from a given observation point.

ISatelliteTimeTicks class

This add time ticks to the trajectory rendering. It is implemented by the Satellite class so that these settings can be handled before an actual trajectory is added to the satellite object. It is also implemented by the SatelliteLayerViewModel class, ticks values set on the viewmodel will be used on all satellites which have no set tick values.

How to create satellite objects

The satellite objects are first created and then added to the satellite layer.

Create only a satellite position object

This test code shows how to create a position object:

var satellite = new TPG.GeoFramework.SatelliteLayer.Satellite
{
   Tle = TestTle(),
   MetadataSetting = (long)SatelliteObject.TleMetadata.All,
   RecalculateTrajectoryAlways = FullCurveCalculations
};
Assert.NotNull(satellite);

//calculate the satellite position for this time
var time = new DateTime(2020, 08, 14, 7, 55, 26, DateTimeKind.Utc);
Assert.IsTrue(satellite.CreateSatellite(time));
Assert.AreEqual(1, satellite.Objects.Count);
Assert.IsNotNull(satellite.Objects.First() as ISatellitePosition);

Create a satellite with both position and trajectory objects

This test code shows how to create a complete satellite:

var satellite = new TPG.GeoFramework.SatelliteLayer.Satellite
{
   Tle = TestTle(),
   MetadataSetting = (long)SatelliteObject.TleMetadata.All,
   RecalculateTrajectoryAlways = FullCurveCalculations
};
Assert.NotNull(satellite);

//calculate the satellite position for this time
var time = new DateTime(2020, 08, 14, 7, 55, 26, DateTimeKind.Utc);
//create trajectory 10 minutes before and after position
var span = new TimeSpan(0, 0, 10, 0);
Assert.IsTrue(satellite.CreateSatellite(time, span, span));
Assert.AreEqual(2, satellite.Objects.Count);
Assert.NotNull(satellite.SatellitePosition);
Assert.NotNull(satellite.SatelliteTrajectory);

Add satellites to the satellite layer

This code shows how to add satellites to the satellite layer:

//The satellite layer
SatelliteLayerViewModel _satelliteLayerViewModel = ...

//create satellites, details not shown
var createdSatellites = new List<ISatellite>();
createdSatellites = CreateSatellites();

//prevent refresh while we are adding satellites
_satelliteLayerViewModel.LockRedraw = true;

foreach (var satellite in createdSatellites)         
   _satelliteLayerViewModel.AddSatellite(satellite);

//will refresh rendering
_satelliteLayerViewModel.LockRedraw = false;


Automatic update of satellite positions

Automatic update of satellite positions is implemented in the SatelliteLayerViewModel (2D layer). The viewmodel handles the actual conversion of time to satellite positions, but the time updating is extracted to a separate class.

SatelliteSystemTimeUpdate

This class updates the satellite system time.

SatellitesStore

This class handles the satellite system time and automatically updating the positions.

Historic satellites

A satellite's TLE is updated over time since the trajectory changes slightly for each turn. It might therefore be of interest to render the different trajectories and positions for a satellite.The historic instances of the satellite will all have the same satellite ID so this property can not be used directly to differentiate between the current satellite object and the historic satellite objects. Instead the historic SatelliteId is set to the original satellite's ID plus an historic offset and an index number equal to number of historic satellites.

ISatelliteHistory class

Adds necessary functionality to differentiate between regular satellite and historic satellites.

public interface ISatelliteHistory : ISatellite
    {
        /// <summary>
        /// Id for historic satellite.
        /// </summary>
        new int SatelliteId { get; set; }

        /// <summary>
        /// The offset added to all historic satellites
        /// </summary>
        int HistoryOffset { get; }

        /// <summary>
        /// The index for historic satellite. There can be many historic satellites for one true satellite id.
        /// </summary>
        int HistoryIndex { get; }

        /// <summary>
        /// Returns false since this is not a regular satellite.
        /// </summary>
        new bool IsSatelliteClass { get; }

        /// <summary>
        /// Returns the non-historic SatelliteId
        /// </summary>
        int RegularSatelliteId { get; }
    }

The IsSatelliteClass will be false for historic satellites and true for regular satellite. For an historic satellite SatelliteId = RegularSatelliteId + HistoryOffset + HistoryIndex. This value can easily be retrieved by RegularSatelliteId.

Create a historic satellite object

The historic satellite is created from the satellite's TLE at a particular time. First create a regular satellite from this TLE and then create the final historic satellite based on the regular satellite.

This test code creates a stand alone historic satellite:

     var satellite = new GeoFramework.SatelliteLayer.Satellite();            
     ...
     var time = new DateTime(2020, 08, 14, 7, 55, 26, DateTimeKind.Utc);
     satellite.Tle = TestTle();
     var span = new TimeSpan(0, 0, 10, 0);
     Assert.IsTrue(satellite.CreateSatellite(time, span, span));
 
     //if this is the first historic satellite with this ID, then historic index is 1
     var historySatellite = new SatelliteHistory(satellite, 1);
     Assert.IsTrue(satellite.IsSatelliteClass);
     Assert.IsFalse(historySatellite.IsSatelliteClass);

This test code is more realistic since it adds the historic satellite to the viewmodel:

     var satellite = new GeoFramework.SatelliteLayer.Satellite();            
     ...
     var time = new DateTime(2020, 08, 14, 7, 55, 26, DateTimeKind.Utc);
     satellite.Tle = TestTle();
     var span = new TimeSpan(0, 0, 10, 0);
     Assert.IsTrue(satellite.CreateSatellite(time, span, span));
 
     //adds the satellite as a historic satellite
     viewmodel.AddSatelliteHistory(satellite, satellite.SatelliteTle.Epoch, true, false);

     //add even simpler
     viewmodel.AddSatelliteHistory(satellite, true, false);

     Assert.AreEqual(1, viewmodel.SatelliteHistories.Count);
     Assert.AreEqual(satellite.SatelliteId, viewmodel.SatelliteHistories[0]);
     var history = viewmodel.SatelliteHistory(satellite.SatelliteId);
     Assert.AreEqual(2, history.Count);

How to analyze the satellites visibility

The analyze functionality is based on observation (line-of-sight) points which tracks one or many satellites. The tracked satellites can be included or excluded from the analyze process. The results from the process can either be used directly or exported to a xml format.

Observation points in the satellite layer

The ISatelliteLineOfSight adds line-of-sight functionality like adding and removing observation points to the satellite layer.

This code adds an observation point to the satellite layer. The point will track the given satellite.

    var satellite = CreateSatellite(time);
    viewmodel.AddSatellite(satellite);
    
    var groundPoint = new GeoUnits.GeoPos(61, 100);
    var id = Guid.NewGuid().ToString();
    var point = new SatelliteLineOfSightViewPoint(id)
    {
        ViewPointName = "nr1",
        Position = groundPoint,
        IncludeInComputation = true
     };

     point.TrackSatellite(satellite.SatelliteId);
     Assert.IsTrue(viewmodel.AddLineOfSightViewPoint(point));

LineOfSightEnabled

Enables or disables all line-of-sight functionality.

LineOfSightViewPoint

Returns the layer's specific line-of-sight point or the line-of-sight point(s) for a specific satellite.

SatelliteInSight

Checks if a specific satellite is tracked by any line-of-sight points or if a specific line-of-sight point tracks a specific satellite.

Defining an observation point

The ISatelliteLineOfSightViewPoint class defines the observation point. The points are handled by the satellite layer.

ViewPointId

A unique value for the view point.

Position

The ground position for the view point.

IncludeInComputation

Controls if the view point is included in the analyze process.

TrackSatellite

Functionality to control which satellites are included in the view point's analyze process.

ComputeSettings

Controls how the analyze process creates visibility results around the view point.

Setting up the analyze process

The class ISatelliteLineOfSightComputeSettings controls the analyze process for a view point. The process can use one or many points around the view point so that a visibility pattern can be created for the area around the view point. In addition the process can analyze the visibility for one or many timestamps so the visibility over time can be analyzed.

This code shows how to analyze specific points around the view point

   var time = new DateTime(2021, 9, 30, 15, 0, 0);
   var satellite = CreateSatellite1(time);

   viewmodel.AddSatellite(satellite);
   var groundPoint = new GeoUnits.GeoPos(61, 100);
   var id = Guid.NewGuid().ToString();
   var point = new SatelliteLineOfSightViewPoint(id)
   {
        ViewPointName = "nr1",
        Position = groundPoint,
        IncludeInComputation = true
    };

    point.ComputeSettings.ComputePoints = new List<GeoPos>();
    point.ComputeSettings.ComputePoints.Add(new GeoUnits.GeoPos(70, 90));
    point.ComputeSettings.ComputePoints.Add(new GeoUnits.GeoPos(60, 90));
    point.ComputeSettings.ComputePoints.Add(new GeoUnits.GeoPos(50, 90));
    point.ComputeSettings.ComputePoints.Add(new GeoUnits.GeoPos(60, 120));
    
    point.ComputeSettings.Times.Add(time);
    point.TrackSatellite(satellite.SatelliteId);
    Assert.IsTrue(viewmodel.AddLineOfSightViewPoint(point));


This code shows how to analyze many points in the area around the view point

    var time = new DateTime(2021, 9, 30, 15, 0, 0);
    var satellite = CreateSatellite1(time);

    viewmodel.AddSatellite(satellite);
    var groundPoint = new GeoUnits.GeoPos(61, 100);
    var id = Guid.NewGuid().ToString();
    var point = new SatelliteLineOfSightViewPoint(id)
    {
        ViewPointName = "nr1",
        Position = groundPoint,
        IncludeInComputation = true
    };

    //add three timestamps that shall be analyzed
    point.ComputeSettings.Times.Clear();
    point.ComputeSettings.Times.Add(time);
    point.ComputeSettings.Times.Add(time.AddMinutes(-1));
    point.ComputeSettings.Times.Add(time.AddMinutes(1));

    point.ComputeSettings.StepCount = 10; // will create 20 x 20 points
    point.ComputeSettings.StepRange = 1000; //mtrs between each point

    //make sure there are not any manual points  
    satelliteLineOfSightViewPoint.ComputeSettings.ComputePoints = null;
    
    point.TrackSatellite(satellite.SatelliteId);
    Assert.IsTrue(viewmodel.AddLineOfSightViewPoint(point));

How to analyze visibility around observation point

The method ISatelliteLineOfSight.ComputeInSight starts the asynchronous process of checking the visibility of a satellite from the observation point.

This test code starts the visibility process:

   ....
   var time = new DateTime(2021, 9, 30, 15, 0, 0);
   var satellite = CreateSatellite1(time);
   viewmodel.AddSatellite(satellite);
   
   var groundPoint = new GeoUnits.GeoPos(61, 100);
   var id = Guid.NewGuid().ToString();
   var point = new SatelliteLineOfSightViewPoint(id)
   {
       ViewPointName = "nr1",
       Position = groundPoint,
       IncludeInComputation = true
   };

   point.ComputeSettings.ComputePoints = new List<GeoPos>();
   point.ComputeSettings.ComputePoints.Add(new GeoUnits.GeoPos(70, 90));
   point.ComputeSettings.ComputePoints.Add(new GeoUnits.GeoPos(60, 90));
   
   point.ComputeSettings.Times.Add(time);
   point.TrackSatellite(satellite.SatelliteId);
   Assert.IsTrue(viewmodel.AddLineOfSightViewPoint(point));

   _processed = false;
   viewmodel.ViewPointsProcessed += ViewmodelOnLineOfSightProcessed;
   viewmodel.ComputeInSight(true);

   Thread.Sleep(2000);
   Assert.IsTrue(_processed);
   viewmodel.ViewPointsProcessed -= ViewmodelOnLineOfSightProcessed;
   Assert.IsTrue(viewmodel.HasSatelliteVisibleComputations);
   
   var results = viewmodel.LineOfSightViewPoint(id);
   Assert.NotNull(results);
   Assert.NotNull(results.Status);
   
   var values = results.Status.Values.ToList();
   ....
}
   

private void ViewmodelOnLineOfSightProcessed(object sender, LineOfSightProcessedEventArgs args)
{
   _processed = true;
}

Example of in sight analyze process: In sight process


When are satellites in the area

The method ISatelliteLineOfSight.ComputeWhenInArea starts the asynchronous process of checking when the satellite is visible from the observation point.

This test code starts the in-area process:

   ....
   var time = new DateTime(2021, 9, 30, 15, 0, 0);
   var satellite = CreateSatellite1(time);
   viewmodel.AddSatellite(satellite);
   
   var groundPoint = new GeoUnits.GeoPos(61, 100);
   var id = Guid.NewGuid().ToString();
   var point = new SatelliteLineOfSightViewPoint(id)
   {
       ViewPointName = "nr1",
       Position = groundPoint,
       IncludeInComputation = true
   };

   point.ComputeSettings.ComputePoints = new List<GeoPos>();
   point.ComputeSettings.ComputePoints.Add(new GeoUnits.GeoPos(70, 90));
   point.ComputeSettings.ComputePoints.Add(new GeoUnits.GeoPos(60, 90));
   
   point.ComputeSettings.Times.Add(time);
   point.TrackSatellite(satellite.SatelliteId);
   Assert.IsTrue(viewmodel.AddLineOfSightViewPoint(point));

   _processed = false;
   viewmodel.ViewPointsProcessed += ViewmodelOnLineOfSightProcessed;
   viewmodel.ComputeWhenInArea(time.AddMinutes(-30), time.AddMinutes(30), new TimeSpan(0, 0, 0, 10), true);

   Thread.Sleep(2000);
   Assert.IsTrue(_processed);
   viewmodel.ViewPointsProcessed -= ViewmodelOnLineOfSightProcessed;
   Assert.IsTrue(viewmodel.HasSatelliteVisibleComputations);
   
   var results = viewmodel.LineOfSightViewPoint(id);
   Assert.NotNull(results);
   Assert.NotNull(results.Status);

   var status = results.Status.First();
   var timeComputations = status.Value.SatelliteComputations.TimeComputations;
   Assert.NotNull(timeComputations);
   ....
}
   

private void ViewmodelOnLineOfSightProcessed(object sender, LineOfSightProcessedEventArgs args)
{
   _processed = true;
}

Check if observation points are in any satellite's footprint

The method ISatelliteLineOfSight.ComputeInFootprints starts the asynchronous process of checking if an observation point is within any footprints.

This test code shows how to start the footprint process

   ....
   
   viewmodel.ComputeInFootprints(true);
   
   ...
   Assert.IsTrue(viewmodel.HasInsideFootprintComputations);

   var results = viewmodel.LineOfSightViewPoint(id);
   var status = results.Status.First();
   var computations = status.Value.FootprintComputations.Computations;
   
    ....

When are satellites inside footprints

The method ISatelliteLineOfSight.ComputeWhenInFootprints starts the asynchronous process of checking when the satellite is inside a footprint.

This test code shows how to start the footprint process

   ....
   
   viewmodel.ComputeWhenInFootprints(startTime, stopTime, steptTime, false);
   
   ...
   Assert.IsTrue(viewmodel.HasInsideFootprintComputations);

   var results = viewmodel.LineOfSightViewPoint(id);
   var status = results.Status.First();
   var computations = status.Value.FootprintComputations.Computations;
   
    ....

Analyze results as XML

The method ISatelliteLineOfSight.StatusAsXml returns the analyze results as XML. The XML content is dependent of type of analyze process.

This test code shows how to get the results as XML

   ....
   viewmodel.ComputeInSight(true);

   ...
   var xml = viewmodel.StatusAsXml();
   ....


EXample of analyze process as XML: XML


Implement satellite functionality in GDK layer

The satellites can easily be integrated in a GDK layer since the handling of the satellites are extracted into a dedicated classes. Automatic updating of all satellites is also extracted from the GDK layer.

Handling satellite objects

The satellites are handled by a dedicated store object that can be shared between several layers, for example 2D and 3D.

The satellite store object

The store object can either be created by the viewmodel or created separately and then passed into the viewmodel through the constructor. SatellitesStore implements the following interfaces: ISatellitesStore, ISatelliteHistories and ISatelliteListeners.

Satellite system time

The system time is extracted into a dedicated class, SatelliteSystemTimeUpdate, which is used by the store object.

Connecting viewmodel and satellite store

Automatical update of satellite time and thereby positions are handled by SatellitesStore and SatelliteSystemTimeUpdate, but the viewmodel might need to be informed about the position changes.

The listener class

The listener class, ISatelliteListeners, handles which objects that shall be informed about position changes.

Position changes information

The class, ISatellitesUpdate, is used to relay information about position changes.

Connecting everything

The following extracted code shows how everything is connected in the SatelliteLayerViewModel.

   public partial class SatelliteLayerViewModel : ... ISatelliteLayerViewModel, ISatellitesUpdate, ...
   { 
      private readonly SatellitesStore _satellitesStore;

      //constructor without store object, layer uses its own store object
      public SatelliteLayerViewModel(IGeoUnitsSetting geoUnitsSetting, IGeoContext geoContext, IGeoNavigator geoNavigator, SatelliteLayerViewFactory satelliteObjectLayerViewFactory) ....
      {
          ...
          _satellitesStore = new SatellitesStore();
          _satellitesStore.AddListener(this);
      }

      //store object is already created, use this object
      public SatelliteLayerViewModel(ISatellitesStore satelliteStore, IGeoUnitsSetting geoUnitsSetting, ....
      {
          ...
          _satellitesStore = (SatellitesStore) satelliteStore;
          _satellitesStore.AddListener(this);
      }

       //starts automatic updating
       public bool StartAutomaticSatellitePositioning(double updateFrequency)
       {
           updateFrequency = updateFrequency < 0.3 ? 0.3 : updateFrequency;
           _satellitesStore.SatelliteSystemTimeUpdate.ResetAutomaticTimerBaseTime();
           _satellitesStore.SatelliteSystemTimeUpdate.SetAndStartTimerTime(updateFrequency * 1000.0, SatelliteLayerSettings, GeoContext);
           IsAutomaticPositioning = true;
       }

       //stop updating
       public bool StopAutomaticSatellitePositioning()
       {
           _satellitesStore.SatelliteSystemTimeUpdate.Stop();  
           IsAutomaticPositioning = false;      
       }


       public override void Update()
       {
           ...
           //must update frequency if scale is changed
           if (ViewportExtentChanged && IsAutomaticPositioning )
           {
               _satellitesStore.SatelliteSystemTimeUpdate.SetAndStartTimerTime(SatelliteLayerSettings, GeoContext);
           }
           ...
       }

       
       //ISatellitesUpdate implementations
       //callback before satellites are automatically updated
       public void BeforeSatellitesUpdate()
       {
           LockRedraw = true;
       }

       //callback after satellites are automatically updated
       public void AfterSatellitesUpdate()
       {
           LockRedraw = false;

           var trackedSatellite = _satellitesStore.TrackedSatellite(TrackSatellite);
           if (trackedSatellite != null)
              CenterToPosition(trackedSatellite as Satellite);
       }
   }

Satellites in 3D

Satellites can also be displayed in 3D. This is handled by the class TPG.Globe3D.SatelliteLayer. When using 2D and 3D in the same application you should make a SatellitesStore outside the layers and inject it in both 2D and 3D layers.

Initializing the 3D satellite layer

The following code example sets up both 2D and 3D satellite layers from the same satellite store.

// Common satellites store
_satellites = new SatellitesStore();

// 2D layer
var satelliteLayerViewFactory = new SatelliteLayerViewFactory(_drawObjectLayerFactory.SymbolsStore);
_satelliteLayerViewModel = new SatelliteLayerViewModel(_satellites, _geoUnitsSetting, geoContext, geoNavigator, satelliteLayerViewFactory);
_mapViewModel.Layers.Add(_satelliteLayerViewModel);

// 3D layer
_satelliteLayer3D = new SatelliteLayer(_map3DViewModel, _satellites, drawObjectDataFactory, stylingXml);

Adding and updating satellites

The 3D layer will display all visible ISatellite objects in the SatellitesStore with the same styling parameters as are used in 2D. The 3D graphics will be updated upon each UpdateSatellites() call in the SatellitesStore, so if you for example control this through an ISatelliteSystemTimeUpdate interface, the satellites will be continuously updated.

If you change any of the styling or other visible parameters of the satellites, you may need to update explicitly, either through a call to SatellitesStore.UpdateSatellites(true) or by calling SatelliteLayer.UpdateSatellites() directly.

Interacting with satellites in 3D

The satellite icons are 3D items of the same type as tracks and point symbols, and you may use the IGlobe3DViewModel.ClickedItem event to respond to mouse clicks on these. Each satellite item will have an ItemId consisting of the SatelliteLayer point list id (accessible through ISatelliteLayer.PointListId) and the satellite ID as InstanceID.

The following example illustrates how you can toggle the 3D label on a clicked satellite:

_map3DViewModel.ClickedItemEvent += clickedSatellite;

private void clickedSatellite(object sender, ClickedItemEventArgs args)
{
    // Only react on right-click
    if (args.MouseButtonEventArgs.ChangedButton != MouseButton.Right)
        return;

    var itemId = args.ItemId;
    if (itemId.ListId == _satelliteLayer3D.PointListId)
    {
        var satId = int.Parse(itemId.InstanceId);
        var sat = _satellites.GetSatellite(satId);
        sat.DrawLabels = !sat.DrawLabels;
        _satellites.UpdateSatellites(true);
    }
}