Click or drag to resize


This tutorial takes you step by step through the process of using STK Components to develop a Windows application in Visual Studio. When you are finished, you will have created a Satellite Visibility Calculator that lets you generate a schedule of viewing opportunities for a selected satellite from a specified location, including the entry and departure azimuth of each visibility interval:

Tutorial Application
Tutorial Application

Among other things, this application provides a good illustration of the use of the classes and methods that STK Components makes available for computing access (visibility) between objects.

Set Up the Project

Setting up the project includes adding references to STK Components libraries, importing data files to be used in azimuth calculations and visualization, and adding the Insight3D component to the toolbox.

  1. In Visual Studio, select New, then Project... from the File menu.

  2. In the New Project dialog, select Visual C# - Windows as the project type and Windows (Forms) Application as the template, give your project a name, and click OK.

    New Project
  3. In the Solution Explorer, change the name of Form1.cs to a name based on your application name, such as SatelliteVisibility.cs. If a dialog appears asking if you want to perform a corresponding rename throughout the project, click Yes.

  4. In the Solution Explorer, right-click on the References folder, and select Add Reference....

  5. In the Add Reference dialog, click the Browse tab, find the Assemblies folder at the STK Components install location, select all the DLL files found there, and click OK.

  6. Right-click on the project in the Solution Explorer, select Add, then select Existing Item... from the context menu.

    Add Existing Item
  7. Browse to the Assemblies subdirectory under the STK Components install location, and select licenses.licx.

  8. Also add facility.png and hs601.mdl following the same procedure used to add the license file. After adding each file, select the file in the Solution Explorer and, in its Properties dialog, set the Copy to Output Directory property to Copy if newer. These files are used to represent the facility and the satellite for the visualization component of the app and can be found in the Examples\Insight3D\HowTo\Data subdirectory under the STK Components install location. From this location, the facility.png file can be found in the Markers subdirectory and the hs601.mdl file can be found in the Models subdirectory.

    File Properties
  9. Select the Toolbox from the View menu. Right-click in the Toolbox and select Choose Items... from the context menu.

    Toolbox Choose Item
  10. Click Browse... button under the .NET Framework Components tab of the Choose Toolbox Items dialog.

    Choose Items
  11. Configure the project for the appropriate CPU architecture x86, x64, or Any CPU. See the Getting Started topic for more details.

  12. Browse to the Assemblies subdirectory under the STK Components install location. Select the AGI.Foundation.Graphics assembly for x86 and Any CPU configurations or AGI.Foundation.Graphics.x64 for x64 configurations. Click OK in the Open dialog and then click OK again in the Choose Toolbox Items dialog.

Create the User Interface

The user interface for this application is shown at the beginning of the tutorial. Consult that illustration as necessary when sizing and positioning the controls you add to the Windows form.

  1. The principal source file for your application should now be open in design view. Resize the form to approximately 600 x 600.

    Tip Tip

    Select the form, open the Properties Window (from the View menu), and scroll down (as necessary) to the Size property to see the dimensions. When you are finished, it is a good idea to keep the Properties Window open. Its contents will change as you select different controls in design view.

  2. Change the form's Text property to "Satellite Visibility Calculator".

  3. Place 2 MaskedTextBox controls in the upper left area of the form (see above illustration) and place a Label to the left of each of them.

  4. Place 3 TextBox controls immediately below the MaskedTextBox controls, and place a Label to the left of each of them.

  5. Edit the Text properties of the 5 labels, from top to bottom, as follows:

    • Start Date (UTC)

    • Stop Date (UTC)

    • Latitude (degrees)

    • Longitude (degrees)

    • Min. Elevation Angle (degrees)

  6. Select the topmost MaskedTextBox, find its Name property in the Properties Window, and change it to "StartBox". Similarly, rename the other MaskedTextBox to "StopBox"

  7. Rename the 3 TextBox controls, from top to bottom, as "LatitudeBox", "LongitudeBox" and "ElevationBox".

  8. Select the topmost MaskedTextBox, click on the small triangle in the upper right corner of the control, and select Set Mask... from the context menu that appears. In the Input Mask dialog, select Short date and time (US), and click OK.

    Set Mask
  9. Similarly, set the mask for the other MaskedTextBox.

  10. Add a new Label to the form, below the other 5, and change its Text property to "Two-Line Elements (TLE)".

  11. Add a TextBox to the right of the new Label, extending to the right edge of the form (see above illustration), and name it "TLEBox". Select the TextBox, click on the small triangle in its upper right corner, and enable the Multiline checkbox in the context menu.

  12. Resize the TextBox so that it will accommodate 2-3 lines of text.

  13. Add a Button, position it at a convenient location (e.g. where shown in the illustration), name it "GoButton", and change its Text property to "Go".

  14. Add a TabControl, covering the bottom area of the form.

  15. Select the TabPages field in the Properties window of the TabControl. Click the ... button that appears as shown below.

    Tab Control Properties
  16. In the TabPage Collection Editor, set the Text property of the first tab to Visualization and the Text property of the second tab to Data.

    Tab Page Editor
  17. Add an Insight3D component to the first tab of the TabControl, covering the entire TabPage, and change the Name property to Insight3D.

  18. Add a ListView control to the second tab of the TabControl, covering the entire TabPage, and name it "OutputBox".

  19. Select the ListView, click on the small triangle in its upper right corner, and, in the context menu that appears, select Details as the View option. Then click Edit Columns to display the ColumnHeader Collection Editor. Click the Add button to add the first column header, then set its Text property to "Start Time (UTC)" and its Width to about 140.

  20. Click the Add button again, set the Text property of the new column to "Entry Azimuth", and set its width to about 140. Similarly, add "Exit Time (UTC)" and "Departure Azimuth" columns, each with a width of about 140. Click OK when you are done.

Code the Application

In this section we will add the code necessary to run the application. In the next section we will run it.

Tip Tip

At the top right corner of each of the code blocks below is a copy icon. Simply click that icon to place the code block on the Windows clipboard, then paste the code into the appropriate location in your source code file. Note that, for clarity, the code blocks given here sometimes include code fragments that have already been inserted automatically by Visual Studio, such as the signature of a method and the closing curly brace. When copying these code blocks into your project, be sure to overwrite those fragments.

  1. Right-click the form in design view and select View Code to expose the class definition.

  2. In the block of using directives at the top of the page, add the directives necessary to support the STK Components classes, methods and properties used in the application:

    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.Windows.Forms;
    //Add using directives for STK Components namespaces:
    using AGI.Foundation;
    using AGI.Foundation.Time;
    using AGI.Foundation.Access;
    using AGI.Foundation.Access.Constraints;
    using AGI.Foundation.Propagators;
    using AGI.Foundation.Platforms;
    using AGI.Foundation.Coordinates;
    using AGI.Foundation.Celestial;
    using AGI.Foundation.Geometry;
    using AGI.Foundation.Graphics;
    using AGI.Foundation.Graphics.Advanced;
  3. Add the following variable declarations at the beginning of the class definition to store user input values, to render primitives in the scene, and compute data for the animation:

    // for user input values.
    private double longitude;
    private double latitude;
    private double minimumElevationAngle;
    private TwoLineElementSet tle;
    private JulianDate startDate;
    private JulianDate endDate;
    // for animation.
    private ModelPrimitive satellite;
    private MarkerBatchPrimitive facility;
    private PolylinePrimitive orbitLine;
    private PolylinePrimitive accessLine;
    private List<Cartesian> orbitPoints;
    // for access computation and animation update.
    private Platform satellitePlatform;
    private Platform facilityPlatform;
    private PointEvaluator satellitePositionEvaluator;
    private AxesEvaluator satelliteOrientationEvaluator;
    private PointEvaluator facilityInertialEvaluator;
    private TimeIntervalCollection accessIntervals;
  4. In design view, double-click on the Start Date MaskedTextBox to expose its MaskInputRejected event handler, and modify it as follows:

    private void StartBox_MaskInputRejected(object sender, MaskInputRejectedEventArgs e)
        MessageBox.Show("Time is specified incorrectly.");
  5. Similarly modify the MaskInputRejected event handler for the Stop Date MaskedTextBox.

  6. In design view, double-click the form and modify its Load event handler as follows:

    private void SatelliteVisibility_Load(object sender, EventArgs e)
        // Get the Earth central body - we need it to compute the approach and departure azimuths,
        // the position of the satellite in the earth's reference frame, and the satellite's
        // orientation axis.
        EarthCentralBody earth = CentralBodiesFacet.GetFromContext().Earth;
        // Create satellite model and polylines for the scene.
        satellite = new ModelPrimitive("hs601.mdl");
        satellite.ReferenceFrame = earth.InertialFrame;
        orbitLine = new PolylinePrimitive(PolylineType.LineStrip, SetHint.Frequent);
        orbitLine.ReferenceFrame = earth.InertialFrame;
        accessLine = new PolylinePrimitive(PolylineType.Lines, SetHint.Frequent);
        accessLine.ReferenceFrame = earth.InertialFrame;
        // Create marker to represent the facility in the scene.
        facility = new MarkerBatchPrimitive(SetHint.Infrequent);
        facility.RenderPass = MarkerBatchRenderPass.Translucent;
        facility.Texture = SceneManager.Textures.FromUri("facility.png");
        // Create the list of points for the orbit polyline.
        orbitPoints = new List<Cartesian>();
        // Add TimeChanged event handler to the SceneManager
        SceneManager.TimeChanged += TimeChanged;
  7. In design view, double-click the Go button and modify its Click event handler as follows:

    private void GoButton_Click(object sender, EventArgs e)
            //Parse the text input from the boxes on the form into appropriate
            //class instances and store them in private variables.
            startDate = GregorianDate.Parse(StartBox.Text).ToJulianDate();
            endDate = GregorianDate.Parse(StopBox.Text).ToJulianDate();
            latitude = Double.Parse(LatitudeBox.Text);
            longitude = Double.Parse(LongitudeBox.Text);
            minimumElevationAngle = Double.Parse(ElevationBox.Text);
            tle = new TwoLineElementSet(TLEBox.Text);
            EarthCentralBody earth = CentralBodiesFacet.GetFromContext().Earth;
            // Create an SGP4 propagator to propagate the TLE.
            Sgp4Propagator propagator = new Sgp4Propagator(tle);
            // Create a Platform for the satellite, including a Point representing the position
            // as reported by the propagator.  The propagator produces raw ephemeris, while the
            // Point enables the results of propagation to work with the GeometryTransformer in
            // order to observe the ephemeris in different reference frames.
            satellitePlatform = new Platform();
            PropagatorPoint point = propagator.CreatePoint();
            satellitePlatform.LocationPoint = point;
            satellitePlatform.OrientationAxes = new AxesVehicleVelocityLocalHorizontal(earth.InertialFrame, satellitePlatform.LocationPoint);
            // Create a facility at the view location.  The longitude and latitude of the facility's
            // location are specified using radians, so convert degrees to radians.
            // Since we don't care about the orientation of the facility, we just
            // use Axes.Root.
            facilityPlatform = new Platform();
            Cartographic location = new Cartographic(Trig.DegreesToRadians(longitude), Trig.DegreesToRadians(latitude), 0.0);
            facilityPlatform.LocationPoint = new PointCartographic(CentralBodiesFacet.GetFromContext().Earth, location);
            facilityPlatform.OrientationAxes = Axes.Root;
            // Call method GetViewingOpportunities to calculate the viewing opportunities from
            // the input parameters.
            List<ViewingOpportunity> opportunities = GetViewingOpportunities();
            // Clear all items from the Output box before writing anything new.
            // Write each viewing opportunity to the output box on the form.
            foreach (ViewingOpportunity opportunity in opportunities)
                ListViewItem item = new ListViewItem(new string[]
            // Update the animation in the Insight3D component.
            // If any of the input values could not be parsed...
            MessageBox.Show("Check your input values!");
    • JulianDate instances are created from the date and time values parsed from the user input, and double values for latitude, longitude and minimum elevation angle are parsed, along with the user-entered TLE. These values are then used in the GetViewingOpportunities and CreateAccessQuery methods, presented below.

    • An Sgp4Propagator is created to propagate the TLE, a Platform object representing the satellite is created, and the LocationPoint property is configured with the output of the propagator's CreatePoint method.

    • Another Platform representing the facility is created, and its LocationPoint property is configured with a PointCartographic, which is created using Earth and a Cartographic object containing the user-entered latitude and longitude values (converted to radians using the DegreesToRadians method) and a value of 0 for altitude.

    • The GetViewingOpportunities method (see below) is called to populate a list of ViewingOpportunity objects containing start and end times and approach and departure azimuth values for each visibility period.

    • The data in each ViewingOpportunity object is displayed line by line in the ListView control on the GUI form.

    • The UpdateVisualization method (see below) is called to reset the animation to account for any new data that was entered.

    Note Note

    This exercise is intended only to illustrate the use of selected STK Components types. It does not necessarily follow coding standards that would apply to a fully developed application. Among other things, the above code includes only minimal error handling. Also, the message box code uses hard-coded strings, making localization to another language and/or locale difficult.

  8. Add the following class to hold data about satellite visibility periods:

    public struct ViewingOpportunity
        public GregorianDate StartDate;
        public GregorianDate EndDate;
        public double ApproachAzimuth;
        public double DepartureAzimuth;
    Note Note

    The above code block and the ones that follow are to be added to the class definition, e.g. after the click event handler for the Go button.

  9. Add a method to retrieve information about visibility periods:

    public List<ViewingOpportunity> GetViewingOpportunities()
        EarthCentralBody earth = CentralBodiesFacet.GetFromContext().Earth;
        // Use the CreateAccessQuery method below to create an AccessQuery object.
        AccessQuery access = CreateAccessQuery();
        //Create the access evaluator. An access evaluator generally needs a specified
        //observer. This is because access computations with light time delays can cause
        //different platforms to have different time intervals for the same constraints.
        //However, AccessQueries that are purely made out of instantaneous links (like here)
        //do not need a specified observer.
        AccessEvaluator evaluator = access.GetEvaluator();
        // Compute the time intervals when the viewing location is able to see the satellite.
        AccessQueryResult accessResult = evaluator.Evaluate(startDate, endDate);
        accessIntervals = accessResult.SatisfactionIntervals;
        // Get an evaluator to find the topographic azimuth, elevation, and range of the satellite
        // as observed by the viewing location.  We'll use this evaluator to evaluate the AER at the
        // start and end of each viewing opportunity interval.
        VectorTrueDisplacement vector = new VectorTrueDisplacement(facilityPlatform.LocationPoint, satellitePlatform.LocationPoint);
        MotionEvaluator<AzimuthElevationRange> aerEvaluator = earth.GetAzimuthElevationRangeEvaluator(vector);
        List<ViewingOpportunity> results = new List<ViewingOpportunity>();
        // Copy the output intervals to the return structure.
        foreach (TimeInterval interval in accessIntervals)
            ViewingOpportunity opportunity = new ViewingOpportunity();
            // Convert the start and stop dates of the interval to UTC GregorianDate instances for 
            // human-readable display.
            opportunity.StartDate = interval.Start.ToGregorianDate();
            opportunity.EndDate = interval.Stop.ToGregorianDate();
            // Compute the azimuth of the satellite observed from the viewing location
            // at the start of the interval.
            AzimuthElevationRange aer = aerEvaluator.Evaluate(interval.Start);
            opportunity.ApproachAzimuth = Trig.RadiansToDegrees(aer.Azimuth);
            // Compute the azimuth of the satellite observed from the viewing location
            // at the end of the interval.
            aer = aerEvaluator.Evaluate(interval.Stop);
            opportunity.DepartureAzimuth = Trig.RadiansToDegrees(aer.Azimuth);
        // Return the ViewingOpportunity instances
        return results;

    We now begin to make direct use of several STK Components types. In the above code:

    • The CreateAccessQuery method (described below) is called to create an AccessQuery, and an AccessEvaluator is created from the AccessQuery.

    • We now compute the visibility intervals. First, the Evaluate method is called, passing the JulianDate values parsed above, which returns a AccessQueryResult object. Then, the SatisfactionIntervals property is retrieved, which contains the time intervals over which access is valid.

    • An evaluator is needed to compute azimuth from the viewing location at the beginning and end of each visibility interval. We get one by calling the GetAzimuthElevationRangeEvaluator method, passing the LocationPoint properties of the two Platform objects (the satellite and facility) participating in the visibility computations, which returns a MotionEvaluator<T>.

    • Finally, we iterate through the TimeIntervalCollection<T> containing the access intervals and create a list of ViewingOpportunity objects. For each interval of access, we convert the start and end date of each interval to a human-readable value in UTC, then evaluate the azimuth-elevation-range evaluator, by calling the Evaluate(JulianDate) method, to calculate the AzimuthElevationRange at the beginning and end of each interval. We then convert the azimuth to degrees, by calling the RadiansToDegrees method, and store the information in our list of ViewingOpportunity objects.

  10. Add the access computation method that was called by the previous method:

    public AccessQuery CreateAccessQuery()
        // Create an Access constraint requiring that the satellite be above a particular elevation
        // angle relative to the local horizontal plane of the facility.
        ElevationAngleConstraint elevationAngleConstraint = new ElevationAngleConstraint();
        elevationAngleConstraint.MinimumValue = Trig.DegreesToRadians(minimumElevationAngle);
        elevationAngleConstraint.MaximumValue = Constants.HalfPi;
        //Create a new link between the satellite and facility, and assign that link to the
        //elevation angle constraint. Then specify that the elevation angle constraint applies
        //to the receiving end of the link (the facility).
        LinkInstantaneous link = new LinkInstantaneous(satellitePlatform, facilityPlatform);
        elevationAngleConstraint.ConstrainedLink = link;
        elevationAngleConstraint.ConstrainedLinkEnd = LinkRole.Receiver;
        return elevationAngleConstraint;

    This method returns the AccessQuery object needed by the GetViewingOpportunities method. In the above code:

  11. Add a method to update the Insight3D visualization:

    private void UpdateVisualization()
        // Reset the Scene in case the button was pressed more than once.
        EarthCentralBody earth = CentralBodiesFacet.GetFromContext().Earth;
        // Get the starting position and orientation of the satellite and set the global evaluators
        // for the TimeChanged event handler.
        satellitePositionEvaluator = GeometryTransformer.ObservePoint(satellitePlatform.LocationPoint, earth.InertialFrame);
        satelliteOrientationEvaluator = GeometryTransformer.GetAxesTransformation(satellitePlatform.OrientationAxes, earth.InertialFrame.Axes);
        facilityInertialEvaluator = GeometryTransformer.ObservePoint(facilityPlatform.LocationPoint, earth.InertialFrame);
        Cartesian satellitePosition = satellitePositionEvaluator.Evaluate(startDate);
        UnitQuaternion satelliteOrientation = satelliteOrientationEvaluator.Evaluate(startDate);
        satellite.Position = satellitePosition;
        satellite.Orientation = satelliteOrientation;
        // Set the scale once, then add it to the Scene.
        satellite.Scale = Math.Pow(10, 5);
        // Set the position once, then add it to the Scene.
        Cartographic facilityPosition = new Cartographic(Trig.DegreesToRadians(longitude), Trig.DegreesToRadians(latitude), 0.0);
        facility.SetCartographic(earth, new Cartographic[] {facilityPosition});
        // Add the satellites initial position to the set of orbit points.
        // Set up animation times.
        SimulationAnimation animation = (SimulationAnimation) SceneManager.Animation;
        if (animation.IsAnimating)
        animation.TimeStep = new Duration(0, 5.0);
        animation.StartCycle = SimulationAnimationCycle.Continue;
        animation.EndCycle = SimulationAnimationCycle.Loop;
        animation.StartTime = startDate;
        animation.EndTime = endDate;
        animation.Time = startDate;
        // Start animation.
    • Call the ResetScene method (defined below) to remove the primitives from the scene in case the Go button has been clicked more than once.

    • Get a PointEvaluator that calculates the satellite's position in the Earth's inertial frame. This evaluator will be used to find the current position of the satellite at the current animation time.

    • Get a AxesEvaluator that calculates the rotation from the satellite's orientation to the Earth's inertial axes. This evaluator will be used to find the current orientation of the satellite at the current animation time.

    • Set the position and orientation of the primitive representing the satellite, and set the scale so it is visible while rotating the Earth, and add it to the list of primitives in the SceneManager to be rendered in the scene.

    • Set the position of the facility from the input latitude and longitude. The SetCartographic method is used to to set the position of the marker, and the marker is added to the list of primitives in the SceneManager to be rendered in the scene.

    • Add the satellites initial position to the set of orbit points, but we do not yet call the Set method on the polyline because we only have one point in the polyline, which will generate an exception. The polyline is updated below in in the TimeChanged event handler.

    • Configure the SceneManagerAnimation to set the StartTime, EndTime, and Time properties based on the user input values, and configure other details of the animation. Call the PlayForward method to start the animation.

  12. Add a method to reset the scene:

    public void ResetScene()
        // If a satellite has been added to the SceneManager,
        // then all three primitives were added.
        if (SceneManager.Primitives.Contains(satellite))
        // The polyline from the satellite to the facility is not always included
        // in the Scene, so must have its own check.
        if (SceneManager.Primitives.Contains(accessLine))
        // Remove the current line representing the orbit.
  13. Add the TimeChanged event handler:

    private void TimeChanged(object sender, TimeChangedEventArgs message)
        // Should not execute if the scene is not animating so the event does not interfere
        // with the AccessQuery.
        if (satellitePositionEvaluator != null && satelliteOrientationEvaluator != null)
            // Set the current position and orientation of the satellite at the current time.
            satellite.Position = satellitePositionEvaluator.Evaluate(message.Time);
            satellite.Orientation = satelliteOrientationEvaluator.Evaluate(message.Time);
            // Add the point to the list of polyline points.
            // If the satellite has access to the facility at the current time, render
            // a polyline from the satellite to the facility.
            if (accessIntervals.Contains(message.Time))
                List<Cartesian> accessPoints = new List<Cartesian>();
                accessLine.Set(accessPoints, new Color[] {Color.Yellow, Color.Yellow}, RenderPassHint.Opaque);
                if (!SceneManager.Primitives.Contains(accessLine))
                // Otherwise, remove any polyline from the satellite to the facility if it exists.
                if (SceneManager.Primitives.Contains(accessLine))
    • Use the PointEvaluator to evaluate the position of the satellite at the current time, and the AxesEvaluator to evaluate its orientation. Set the position and orientation of the primitive representing the satellite, add the position to the list of orbit line points, and update the polyline primitive.

    • Call the TimeIntervalCollectionContains to determine whether the satellite has access to the facility at the current time, which determines whether to draw a line representing access between the satellite and facility.

    • If access is valid, create a list of Cartesian representing the positions of the facility and the satellite. Call the Set method on the polyline representing access line to set the points and colors of the line. If the SceneManager does not contain the polyline already, then add it to the collection of primitives.

    • Otherwise, remove the access line from the SceneManager if it exists in the primitives collection.

Run the Application

We are now ready to test the application and see how it works:

  1. Build the application and run it (press F5 or select Start Debugging from the Debug menu).

  2. Enter a date/time range, the latitude and longitude of the viewing site and the minimum elevation angle, i.e. the minimum angle above the horizon at which the satellite can be viewed.

  3. Enter a Two-Line Element Set (TLE) to define the satellite's orbit. Numerous TLEs can be found at the CelesTrack web site on the Current NORAD Two-Line Element Sets page. Simply copy and paste the TLE of interest into the text box.

    TLE for testing: If you are not connected to the internet, or would like to test the application quickly without going online, the following TLE has tested successfully for a date range of June 1-30, 2007, a latitude of 40 degrees, a longitude of -76 degrees, and a minimum elevation angle of 5 degrees:

    MOLNIYA 3-39
    1 20813U 90084A   07157.52757149 -.00000978  00000-0  10000-3 0  8139
    2 20813  62.3585 177.5907 7234421 261.2958  18.3008  2.01001663122427
  4. Click the Go button.

  5. If no results (and no error messages) display, try another TLE or adjust your date/time range, viewing location and/or elevation angle.

A simple application has been used here to prevent the tutorial from becoming overly complex or lengthy. For more ambitious uses of STK Components see the other included Example Applications.