Click or drag to resize

Satellite Tracker

This web application generates ground tracks on an embedded Google map, as well as ephemeris tables, for a user-selected satellite viewed from a user-defined location during a user-specified time period.

Satellite Tracker

The source code files for the application can be found in the STK Components install at Examples\SatelliteTracker\.

Since the code is fully commented, only selected classes, properties and methods are discussed here briefly.

Identifying Viewing Opportunities

Among other things, the application identifies time intervals when the satellite can be viewed from a defined location. As illustrated in the above figure, portions of the satellite's ground track corresponding to periods when it can be seen from the defined location are highlighted in a different color than the rest of the ground track, and information about those periods of visibility, including the azimuth and elevation of the satellite's approach and departure, is listed in a table.

The web service that generates ephemeris and identifies viewing opportunities includes a method that returns an array of structures, each containing the start and end times of a visibility period and the approach and departure azimuth and elevation:

C#
public struct ViewingOpportunity
{
    public double StartJulianDateUtc;
    public double EndJulianDateUtc;
    public double ApproachAzimuth;
    public double DepartureAzimuth;
}

[WebMethod(Description = "Find the times during which a satellite is viewable from a specified location.")]
public ViewingOpportunity[] GetViewingOpportunities(string tleString,
                                                    double startSearchJulianDateUtc, double endSearchJulianDateUtc,
                                                    double viewingLongitudeDeg, double viewingLatitudeDeg, double minViewableElevationAngleDeg)
{

The GetViewingOpportunities method begins by instantiating a UserInput class that includes a method to compute access (visibility) between objects. The method then assigns some of its inputs to the corresponding UserInput properties and invokes that class's CreateAccessQuery() method:

C#
UserInput input = new UserInput
{
    Tle = tleString,
    Longitude = viewingLongitudeDeg,
    Latitude = viewingLatitudeDeg,
    MinimumElevationAngle = minViewableElevationAngleDeg
};
AccessQuery access = input.CreateAccessQuery(out satellite, out facility);

The latter method creates Platform objects representing the user-specified satellite and viewing location, creates an ElevationAngleConstraint instance based on user input for the minimum elevation angle, and returns an AccessQuery instance:

C#
public AccessQuery CreateAccessQuery(out Platform satellite, out Platform facility)
{
    // Create an SGP4 propagator to propagate the TLE.
    Sgp4Propagator propagator = new Sgp4Propagator(new TwoLineElementSet(Tle));
    satellite = new Platform();
    facility = new Platform();

    // Create 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.
    satellite.LocationPoint = propagator.CreatePoint();
    satellite.OrientationAxes = Axes.Root;

    // Create a facility at the viewing location. The longitude and latitude of the facility's
    // location are specified using radians, so convert degrees to radians.
    facility.LocationPoint = new PointCartographic(
        CentralBodiesFacet.GetFromContext().Earth,
        new Cartographic(
            Trig.DegreesToRadians(Longitude),
            Trig.DegreesToRadians(Latitude),
            0.0));
    facility.OrientationAxes = Axes.Root;

    // 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 the link between the two platforms, we will ignore time delay affects from
    // speed of light and make an instantaneous link. It does not matter which is the
    // transmitter and which is the receiver, but the elevation angle constraint must be
    // applied to the facility. In this case we will assume the facility is the receiver.
    LinkInstantaneous link = new LinkInstantaneous(satellite, facility);
    elevationAngleConstraint.ConstrainedLink = link;
    elevationAngleConstraint.ConstrainedLinkEnd = LinkRole.Receiver;

    return elevationAngleConstraint;
}

The same STK Components types are used in substantially the same way in the Tutorial. Please see that topic for details on how the access computation works.

Color Coded Ground Tracks

An abstract class, ColorCodedGroundTrack, is provided to facilitate generating a ground track and for highlighting portions of it during access periods. It includes a Compute() method to compute the ground track and the highlighting, calling several virtual methods in the process:

C#
public void Compute()
{
    // Obtain the Earth for the current context (thread).
    EarthCentralBody earth = CentralBodiesFacet.GetFromContext().Earth;

    // Get an evaluator that tells us the location of the satellite's point
    // in the Earth Inertial reference frame. We wrap this up in a using
    // block so that that evaluator is automatically disposed when we're done.
    // Then, get another evaluator, this one to tell us the location of the satellite
    // in longitude, latitude, and altitude coordinates relative to the Earth.
    using (PointEvaluator satelliteInInertialEvaluator = GeometryTransformer.ObservePoint(m_satellite.LocationPoint, earth.InertialFrame))
    using (MotionEvaluator<Cartographic> satelliteInCartographicEvaluator = earth.ObserveCartographicPoint(m_satellite.LocationPoint))
    {
        // Tell derived classes that we're starting computation.
        BeginCompute();

        // If there are no highlighted intervals, or if we're starting before the
        // first one, then the ground track starts off non-highlighted.
        if (Intervals.Count == 0 || GroundTrackInterval.Start < Intervals[0].Start)
        {
            SetHighlighted(false);
        }
        else
        {
            SetHighlighted(true);
        }

        // the index of the next interval in Intervals watch for.
        int nextInterval = 0;

        // true if we're looking for the start of the interval indicated by
        // nextInterval, false if we're looking for the end of it.
        bool lookingForStart = true;

        // Loop over all of the ground track dates based on GroundTrackInterval
        // and TimeStep. Use TimeGenerator as a convenient way to enumerate
        // these dates.
        foreach (JulianDate date in TimeGenerator.FromInterval(GroundTrackInterval.Start, GroundTrackInterval.Stop, TimeStep))
        {
            // Find the interval (or space between intervals) containing this date.
            // We may need to generate additional ground track points as we move
            // through the intervals in order to accurately represent that transition
            // from highlighted to non-highlighted (or vice-versa).
            bool keepMoving = true;
            while (keepMoving && nextInterval < Intervals.Count)
            {
                // Start off assuming we found the correct interval
                // (or space between intervals).
                keepMoving = false;

                // If we're looking for the start of an interval and the current
                // date is on or after the start of the 'nextInterval'...
                if (lookingForStart && date >= Intervals[nextInterval].Start)
                {
                    // Add a final non-highlighted point at the start of the
                    // highlighted interval.
                    AddPointInternal(Intervals[nextInterval].Start, satelliteInInertialEvaluator, satelliteInCartographicEvaluator);

                    // Switch to highlighted.
                    SetHighlighted(true);

                    // Add a highlighted point at the start of the highlighted
                    // interval, but only if the current 'date' and the start
                    // of the interval are not identical. If they are, the
                    // necessary point will be added below so adding it here, too,
                    // would be redundant.
                    if (date > Intervals[nextInterval].Start)
                    {
                        AddPointInternal(Intervals[nextInterval].Start, satelliteInInertialEvaluator, satelliteInCartographicEvaluator);
                    }

                    // Now we're looking for the end of this interval.
                    lookingForStart = false;
                }

                // If we're looking for the end of an interval and the current
                // date is on or after the end of the 'nextInterval'...
                if (!lookingForStart && date >= Intervals[nextInterval].Stop)
                {
                    // Add a final highlighted point at the end of the
                    // highlighted interval.
                    AddPointInternal(Intervals[nextInterval].Stop, satelliteInInertialEvaluator, satelliteInCartographicEvaluator);

                    // Switch to non-highlighted.
                    SetHighlighted(false);

                    // Add a non-highlighted point at the end of the highlighted
                    // interval, but only if the current 'date' and the end
                    // of the interval are not identical. If they are, the
                    // necessary point will be added below so adding it here, too,
                    // would be redundant.
                    if (date > Intervals[nextInterval].Stop)
                    {
                        AddPointInternal(Intervals[nextInterval].Stop, satelliteInInertialEvaluator, satelliteInCartographicEvaluator);
                    }

                    // Now we're looking for the start of the next interval.
                    lookingForStart = true;
                    ++nextInterval;
                }
            }

            // Add the current ground track point.
            AddPointInternal(date, satelliteInInertialEvaluator, satelliteInCartographicEvaluator);
        }

        // Tell derived classes that we're done computing.
        EndCompute();
    }
}

The following classes are derived from the above class:

  • ResultsGroundTrack - generates the contents of the Results page, including the ground track for display on the Google Map embedded in the web page and the table of ephemeris points.

  • GoogleEarthGroundTrack - generates a Google Earth KML file with a viewing location and a satellite ground track. The ground track is highlighted to indicate those periods during which the satellite is visible from the viewing location.

Satellite Database

This web application lets the user select the satellite of interest from a specified category:

Satellite Tracker Select Satellite

The functionality utilized here is provided by the SatelliteDatabase class:

C#
public static class SatelliteDatabase
{
    /// <summary>
    /// Gets the categories of spacecraft in the database. The categories are the
    /// "missions" in the STK satellite database.
    /// </summary>
    public static ReadOnlyCollection<string> GetCategories()
    {
        // Filter missions and only return the ones that actually contain
        // spacecraft with two-line element sets (TLEs).
        List<string> result = GetDatabase().GetMissions()
                                           .Where(mission => GetSatellitesInCategory(mission).Count > 0)
                                           .ToList();

        // Sort the categories alphabetically.
        result.Sort();

        return result.AsReadOnly();
    }

    /// <summary>
    /// Gets the common names of all spacecraft with TLEs in a particular category.
    /// </summary>
    /// <param name="category">The category (or mission) of the spacecraft.</param>
    /// <returns>A list of spacecraft common names in the category.</returns>
    public static ReadOnlyCollection<string> GetSatellitesInCategory(string category)
    {
        // Build a satellite database query requiring that the Mission field
        // match the requested category.
        StkSatelliteDatabaseQuery query = new StkSatelliteDatabaseQuery
        {
            Mission = new Regex(category)
        };

        // Filter the entries matching the query and add the ones that have
        // two-line element sets (TLEs).
        List<string> result = GetDatabase().GetEntries(query)
                                           .Where(entry => entry.TwoLineElementSet != null)
                                           .Select(entry => entry.CommonName)
                                           .ToList();

        // Sort the spacecraft names alphabetically.
        result.Sort();

        return result.AsReadOnly();
    }

    public static string GetTleString(string category, string satelliteName)
    {
        // Build a satellite database query requiring that the Mission field
        // match the requested category and the CommonName field match the
        // requested satellite name.
        StkSatelliteDatabaseQuery query = new StkSatelliteDatabaseQuery
        {
            Mission = new Regex(category),
            CommonName = new Regex(satelliteName)
        };

        // Filter the entries matching the query and return the first one that
        // has a two-line element set (TLE).
        // Return empty string if no matches, or no matches had TLEs.
        return GetDatabase().GetEntries(query)
                            .Where(entry => entry.TwoLineElementSet != null)
                            .Select(entry => entry.TwoLineElementSet.ToTleString())
                            .FirstOrDefault() ?? "";
    }

    /// <summary>
    /// Gets the STK satellite database instance.
    /// </summary>
    private static StkSatelliteDatabase GetDatabase()
    {
        // Open the satellite database named "stkSatDb" in the "bin/Data/SatelliteDatabase" subdirectory.
        return new StkSatelliteDatabase(HttpContext.Current.Server.MapPath("~/bin/Data/SatelliteDatabase"), "stkSatDb");
    }
}