Programming Virtual Earth: Writing a Vista Gadget that tracks the International Space Station
This article is based on a previous article published in the MP2K Magazine.
Download the gadget complete with source code: ISS_Tracker.gadget.zip (31KB).
Introduction
This is the second part of two articles about writing Vista Gadgets that use Virtual Earth. The first part ("Programming Virtual Earth: Writing a Vista Gadget") showed you how to write a Windows Vista Gadget that showed a simple interactive map using Virtual Earth. This second part extends the gadget into something more useful: A Tracker for the International Space Station (ISS).
At start up, the gadget automatically downloads the latest orbital parameters from the AmSat website. The map is updated every half second with the ISS's latest position. The position is tracked with a pushpin shaped like the ISS. The map automatically pans to keep the ISS centered in the map. The user may zoom in or out using "+" and "-" buttons. A "Refresh" button allows the orbital parameters to be refreshed from AmSat. These orbital parameters should only be refreshed every few days - you do not need to do this if you restart Vista (and hence the gadget) on a daily basis.
Writing the Vista Gadget
The gadget source code has been expanded and now includes ten files. One of these is an image: ISS_pin.gif stores the image of the ISS pushpin. The remaining extra files (global.js, graphic_clock.js, math.js, sgp4sdp4.js, time.js, utils.js, view.js ) are all JavaScript files used to calculate the orbit of the ISS. These are based on the orbit calculation routines available from http://www.movingsatellites.com.
This gadget may seem as if it is considerbaly more complicated that the previous gadget. In reality it has the same overall structure. For example, here is our new manifest file:
<?xml version="1.0" encoding="utf-8" ?>
<gadget>
<name>ISS Tracker</name>
<namespace>Example.You</namespace>
<version>1.0</version>
<author name ="Richard Marsden">
<info url="http://www.mapping-tools.com/info/prog/VE/ISS_gadget.shtml" />
</author>
<copyright>2007</copyright>
<description>International Space Station Tracker that uses Virtual Earth</description>
<hosts>
<host name="sidebar">
<base type="HTML" apiVersion="1.0.0" src="ISS_Tracker.html" />
<permissions>full</permissions>
<platform minPlatformVersion="0.3" />
</host>
</hosts>
</gadget>
The only change of consequence is that the main HTML source filename has been changed to "ISS_Tracker.html".
In turn, ISS_Tracker.html is based on the HTML file in our previous gadget. However, we need to add code to download the latest orbital data, make calls to the MovingSatellites.com code to calculate new positions, and to automatically update the map. The orbital data is specified using NORAD "TLE" (Two Line Element) Keplerian orbital parameters. This is a standard way of defining a satellite in Earth orbit in a machine-readable text form. A typical set of TLE parameters look like this:
ISS
1 25544U 98067A 07055.29853167 .00017324 00000-0 99505-4 0 7052
2 25544 051.6356 327.3797 0020641 244.1596 211.2169 15.78603656472945
The first line specifies the satellite name. The next two lines specify the actual parameters in a fixed format reminiscent of Fortran. These parameters can be obtained from a number of websites, including NORAD's, but we shall get ours from the AmSat site. The URL for the file is: http://www.amsat.org/amsat/ftp/keps/current/nasa.all. We fetch this file using the ActiveX Microsoft.XMLHTTP object. Note that this is specific to Microsoft JScript. All Vista gadgets will use JScript, but the ActiveX control will not necessarily be available in a traditional web browser environment (eg. Mozilla).
So let's get to the main ISS_Tracker.html gadget file. Here is the beginning. As well as including the Virtual Earth control, we include the orbit calculation JScript files:
<html>
<head>
<title>International Space Station Track Gadget for Vista</title>
<!-- This is a Vista Gadget that uses Virtual Earth to track the International Space Station (ISS) -->
<!-- Author: Richard Marsden 2007. Website: http://www.mapping-tools.com/info/prog/VE/ISS_gadget.shtml -->
<script src="http://dev.virtualearth.net/mapcontrol/v3/mapcontrol.js"></script>
<!-- Orbit Calculation Code based on that at MovingSatellites.com -->
<script language="JavaScript" src="global.js"></script>
<script language="JavaScript" src="math.js"></script>
<script language="JavaScript" src="time.js"></script>
<script language="JavaScript" src="utils.js"></script>
<script language="JavaScript" src="sgp4sdp4.js"></script>
<script language="JavaScript" SRC="graphic_clock.js"></script>
<script language="JavaScript" SRC="view.js"></script>
Next we get into our main JavaScript code with some global definitions and parameters:
<script>
var map=null;
// We put the NORAD TLE (orbital parameters) into these two lines to be parsed
var TLE_Line1 = "";
var TLE_Line2 = "";
var m_bInitialized = false;
var m_SatelliteData; // Use this to encapsulate the satellite position in a useful form
var m_TimerID = 0;
var pinSet = false; // Has the ISS pushpin been set?
var xmlhttp;
var pgTXT = "";
var bFirstLoad = true;
var UpdatePeriod = 500; // 0.5 seconds - How often to update the map (in ms)
// The URL to fetch the satellite TLE data
var url = "http://www.amsat.org/amsat/ftp/keps/current/nasa.all";
// The name of the satellite we want in the above file
var sSatellite = "ISS";
Most of these are probably self explanatory. "map" refers to the Virtual Earth Control. "TLE_Line1" and "TLE_Line2" are used to pass the TLE orbital parameters to the orbit calculation code. "pinSet" is a flag used to monitor whether we already have a pushpin for the ISS. We need to know this when moving the pushpin to a new location.
"UpdatePeriod" is a parameter that defines the time (in milliseconds) between map updates. It is currently set to 500ms (half a second). "url" specifies the URL of the TLE data file. You can change this to a different data provider if you wish. "sSatellite" specifies the name of the required satellite in the TLE data file. Note that the same satellite might have different names in different files. For example, the ISS is still occasionally referred to as "Zarya".
Unfortunately JavaScript has very limited number formatting abilities. We need to limit the number of decimal places displayed when displaying the ISS's coordinates. Hence we need to define a simple utility to convert floating point numbers into fixed strings with 4 decimal places. Here is the utility:
function FloatToString( f )
{
var str,i;
str = f.toString();
i = str.indexOf(".");
if (i<0)
{
return str;
}
i = i + 5
if (i>=str.length)
{
return str;
}
str = str.substr(0, i)
return str;
}
We fetch the TLE data using the Microsoft.XMLHTTP ActiveX object. This is asynchronous, so we have to define a callback that handles the data when it has been received. This is called stateChange() and is defined as follows:
function stateChange()
{
if (xmlhttp.readyState==4)
{
if (xmlhttp.status==200)
{
pgTXT = xmlhttp.responseText;
// Extract the required orbital parameters
var orbitalLines = pgTXT.split("\n");
var i=0;
var bNotFound=true;
for (i=0; i<orbitalLines.length && bNotFound; i++)
{
if (orbitalLines[i] == sSatellite)
{
TLE_Line1 = orbitalLines[i+1];
TLE_Line2 = orbitalLines[i+2];
bNotFound = false;
}
}
if (bFirstLoad)
{
createMap();
}
else
{
LoadSatelliteData();
ReDisplay();
}
}
}
}
This function checks to see if the callback represents a successful data fetch. If successful, it then scans through the data looking for a line that matches "sSatellite". When found, the two lines of the TLE are extracted. The map is then created or refreshed.
The start-up process is controlled by the following two functions:
function getOrbitalElements()
{
TLE_Line1="";
TLE_Line2="";
pgTXT = "";
xmlhttp = null;
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
if (xmlhttp!=null)
{
xmlhttp.onreadystatechange = stateChange;
xmlhttp.open("GET",url,true);
xmlhttp.send(null);
}
}
function onPageLoad()
{
getOrbitalElements();
}
onPageLoad() is a callback that is called when the gadget is created. Here we simply call getOrbitalElements(). This creates the Microsoft.XMLHTTP ActiveX object to fetch the orbital data. When the data is fetched, the stateChange() callback is called. This then calculates the orbital data and creates the map.
The map is created using the function createMap():
function createMap()
{
// Load the satellite data from TLE_Line1 & TLE_Line2
LoadSatelliteData();
// Fetch the current date/time in Julian Date form
var jdJulian = CurrentJulianDate();
// Calculates the current satellite position
var ret = CalculateSatellitePosition ( jdJulian );
CalculateLatLonAlt( jdJulian );
// Extract the satellite position: Note that the longitude sign needs correcting
m_SatelliteData.fActualLat = m_lla[0];
m_SatelliteData.fActualLon = -m_lla[1]; // Correct the Longitude Sign (VE: West=negative)
m_SatelliteData.fActualAlt = m_lla[2];
// Display the current longitude and latitude of the satellite
gadgetContent.innerText = "LA="+FloatToString(m_SatelliteData.fActualLat) + " LG=" + FloatToString(m_SatelliteData.fActualLon);
// Display the VE map: Aerial photo, no dashboard, centred on the satellite position
map = new VEMap('myMap');
map.LoadMap(new VELatLong( m_SatelliteData.fActualLat, m_SatelliteData.fActualLon),5, 'h', false);
map.HideDashboard();
// Add a pushpin for the satellite
var pin = new VEPushpin(1, new VELatLong(m_SatelliteData.fActualLat, m_SatelliteData.fActualLon), 'ISS_pin.gif', 'ISS', 'Current Location', '.pinClass');
map.AddPushpin(pin);
pinSet = true;
bFirstLoad = false;
// Set the callback for an update in a short period of time (set by UpdatePeriod)
setTimeout("ReDisplay()", UpdatePeriod);
}
This loads the orbital data into the MovingSatellites.com code, gets the time, and calculates an initial orbital position. The position is stored in m_SatelliteData for convenience. The gadget body tag contains a span tag called "gadgetContent". This is positioned between the buttons and the map, and displays the ISS's coordinate position as a line of text. The "gadgetContent.innerText=" performs this display.
We then create a new Virtual Earth Map object (VEMap), and center the map on the ISS's current position. A pushpin is also created at this position using the ISS_pin.gif image.
The final "setTimeout" line sets a call-back to the ReDisplay() function for a map update. The update interval is set by the UpdatePeriod parameter.
The implementation of ReDisplay() is actually very similar to createMap(). The orbital data does not have to be loaded, and the map does not have to be created. However, the old ISS pushpin does have to be deleted, and the map has to be panned to the new position.
function ReDisplay()
{
var jdJulian = CurrentJulianDate();
var ret = CalculateSatellitePosition( jdJulian );
CalculateLatLonAlt( jdJulian );
m_SatelliteData.fActualLat = m_lla[0];
m_SatelliteData.fActualLon = -m_lla[1]; // Note: longitude
m_SatelliteData.fActualAlt = m_lla[2];
if (pinSet)
{ // Delete the satellite pushpin - if it already exists
map.DeletePushpin(1);
}
// Update the coordinate position
gadgetContent.innerText = "LA="+FloatToString(m_SatelliteData.fActualLat) + " LG=" + FloatToString(m_SatelliteData.fActualLon);
// Create a new satellite pin, and pan the VE map to centre on this position.
var newLoc = new VELatLong(m_SatelliteData.fActualLat, m_SatelliteData.fActualLon);
var pin = new VEPushpin(1, newLoc, 'ISS_pin.gif', 'ISS', 'Current Location');
pinSet = true
map.AddPushpin(pin);
map.PanToLatLong( newLoc );
newLoc = 0;
// Call back for the next update
setTimeout("ReDisplay()", UpdatePeriod);
}
The JavaScript finishes with the three button callbacks. The "+" and "-" zoom buttons are identical in action and implementation as before. However, the new "Refresh" button requires a new call back. This is just as simple, and calls getOrbitalElements() to re-fetch the orbital parameters.
function ZoomInButton()
{
map.ZoomIn();
}
function ZoomOutButton()
{
map.ZoomOut();
}
function RefreshElementsButton()
{
getOrbitalElements();
}
The body section of the gadget is very similar to the previous Virtual Earth example. The body tag itself defines a call-back to onPageLoad(). We also have the two zoom buttons, and an empty div tag to hold the Virtual Earth map. New additions are a third button ("Refresh") and a span tag to display the ISS's coordinate position as a line of text.
<body style="width:140;height:270;" onload="onPageLoad();">
<input type="button" value="+" style="float:left;" onclick="ZoomInButton()" ID="ZoomInButton" Name="ZoomInButton" />
<input type="button" value="-" style="float:left;" onclick="ZoomOutButton()" ID="ZoomOutButton" Name="ZoomOutButton" />
<input type="button" value="Refresh" onclick="RefreshElementsButton()" ID="RefreshButton" Name="RefreshButton" />
<br/>
<span id="gadgetContent" style="clear:both; font-size:10">Loading data...</span>
<div id='myMap' style="position:relative; width:140px; height:250px;"></div>
</body>
</html>
And that is it! A few simple steps have made the Virtual Earth gadget into a useful little tracking tool.
Download the gadget complete with source code: ISS_Tracker.gadget.zip (31KB).
Return to the Main MapPoint Articles Page.

