Create a satellite tracker from scratch in 30 lines of JavaScript
Tags: javascript, glitch, tutorial
Published: 2021-05-06 02:32:25

This tutorial will walk you through how to create a web app that visualizes the location of any satellite in real-time, like the International Space Station. 

We're going to do this from scratch, using the same techniques a real rocket scientist would! 

1. We'll look at where to get the data about individual satellites that the government publishes, known as **Two-Line Element Sets**, or TLE's. 
2. We'll use [satellite-js](https://github.com/shashwatak/satellite-js) to predict the orbit of the satellite given the TLE's (this is the rocket science part).
3. We'll use [CesiumJS](https://github.com/CesiumGS/cesium#readme) to visualize the result, but you can use any library/engine that can take in longitude, latitude, and height. 

Below is a preview of the final result.

{% glitch satellite-viewer app %}

This is showing the path of the International Space Station, sped up by 40x. To **see its current location in real-time**, click the clock icon at the top-left of the clock wheel. 

Here's a [direct link to the app](https://satellite-viewer.glitch.me/). And the [source code on Glitch](https://glitch.com/edit/#!/satellite-viewer).

## 1 - Get the satellite's Two-Line Element Set

A Two-Line Element Set, or TLE, is a data format that describes the motion of an object orbiting the Earth. It was created by the North American Aerospace Defense Command (NORAD). You can [read more about it and its history here](https://celestrak.com/columns/v04n03/).

Given this description of the orbit, we can predict the location of where it's going to be at any moment in time (which is step 2 below).

**This means that most "live" satellite trackers are not live in the same way that tracking a delivery car on a map is**. Instead of relying on constantly receiving position updates, those who track objects in space will often get the latest TLE's (which are regularly updated) and use that to predict where the object is right now.

Where do we get the TLE's? There is no one global official registry. Whoever owns the satellite and is monitoring it is responsible for updating and publishing the TLE for the benefit of the global space community (unless it's a spy satellite). 

We can find these TLE's on [Space Track](https://www.space-track.org) which is a registry run by the United States Space Command. 

Another source is [this list on CeleStrak](https://celestrak.com/NORAD/elements/) maintained by Dr. T.S. Kelso.

We're going to use CeleStrak since it doesn't require a login. To find the TLE for the International Space Station, click on the [Space Stations](https://celestrak.com/NORAD/elements/stations.txt) link. 

The first one is the TLE for the ISS:

```
ISS (ZARYA)             
1 25544U 98067A   21122.75616700  .00027980  00000-0  51432-3 0  9994
2 25544  51.6442 207.4449 0002769 310.1189 193.6568 15.48993527281553
```

The meaning of these numbers is [listed in table 1 in Dr T.S. Kelso's column](https://celestrak.com/columns/v04n03/#FAQ01). Most of them are identifiers and metadata about the satellite, like when it was launched. 

You can find TLE's for weather satellites, GPS satellites, and even [SpaceX's Starlink constellation](https://celestrak.com/NORAD/elements/starlink.txt) in this same format.

## 2 - Predict the satellite orbit 

Now that you know how to get the TLE of the object you're interested in tracking, the next step is converting that to a position in time.

We're going to use [satellite-js](https://github.com/shashwatak/satellite-js) for this. 

Include the library from a CDN:

```html
<script src="https://cdnjs.cloudflare.com/ajax/libs/satellite.js/4.0.0/satellite.min.js"></script>
```

Then pass the TLE to it, and a time:

```javascript
const ISS_TLE = 
    `1 25544U 98067A   21122.75616700  .00027980  00000-0  51432-3 0  9994
	 2 25544  51.6442 207.4449 0002769 310.1189 193.6568 15.48993527281553`;
// Initialize the satellite record with this TLE
const satrec = satellite.twoline2satrec(
  ISS_TLE.split('\n')[0].trim(), 
  ISS_TLE.split('\n')[1].trim()
);
// Get the position of the satellite at the given date
const date = new Date();
const positionAndVelocity = satellite.propagate(satrec, date);
const gmst = satellite.gstime(date);
const position = satellite.eciToGeodetic(positionAndVelocity.position, gmst);

console.log(position.longitude);// in radians
console.log(position.latitude);// in radians
console.log(position.height);// in km
```

Now we have the position of the satellite at the current time, `new Date()`.

This position is produced as a result of simulating a specific model of satellite motion. This model is called SGP4/SDP4. All TLE's assume this specific model. 

If you're wondering about the accuracy of this model, the short answer is, [it depends](https://celestrak.com/columns/v04n05/#FAQ06).

> Accuracy of the two-line element sets is dependent upon a number of factors. These range from the particular sensors used and amount of data collected to the type of orbit and condition of the space environment. Unfortunately, since these factors vary for each element set, so does the accuracy. While NORAD has experimented with methods to incorporate prediction quality into the element sets, none of these methods has yet proved successful.


## 3 - Visualize the result

Now we have a way to get the location of any satellite, at any given time. We can pass in future times to animate its path, which we'll do in the next step.

First, let's see how to visualize an individual point in space using CesiumJS.

We load the library from CDN:

```html
<script src="https://cesium.com/downloads/cesiumjs/releases/1.81/Build/Cesium/Cesium.js"></script>
  <link href="https://cesium.com/downloads/cesiumjs/releases/1.81/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
```

And create a container element:

```html
<div id="cesiumContainer"></div>
```

We then initialize the viewer. Here we pass in some extra options to disable functionality that requires an access token:

```javascript
// Initialize the Cesium viewer.
const viewer = new Cesium.Viewer('cesiumContainer', {
  imageryProvider: new Cesium.TileMapServiceImageryProvider({
    url: Cesium.buildModuleUrl("Assets/Textures/NaturalEarthII"),
  }),
  baseLayerPicker: false, geocoder: false, homeButton: false, infoBox: false,
  navigationHelpButton: false, sceneModePicker: false
});
viewer.scene.globe.enableLighting = true;
```

Finally, we'll visualize the satellite position as a red dot in space:

```javascript
const satellitePoint = viewer.entities.add({
  position: Cesium.Cartesian3.fromRadians(
    position.longitude, position.latitude, position.height * 1000
  ),
  point: { pixelSize: 5, color: Cesium.Color.RED }
});
```

See the [complete source code of this step in simple.html on Glitch](https://glitch.com/edit/#!/satellite-viewer?path=simple.html).

## 4 - Animate the path

To animate the path, we just need to sample more positions in the future. CesiumJS has a built-in way to interpolate between these samples over time.

The setup for this is a bit verbose. [You can see the full code on Glitch](https://glitch.com/edit/#!/satellite-viewer?path=index.html%3A6%3A82). The important concepts are described below.

We create a `SampledPositionProperty`. This is an object that will hold position samples over time and will interpolate between them:

```javascript
const positionsOverTime = new Cesium.SampledPositionProperty();
```

We loop through however many samples we want to get, and for each sample, we construct a time object, called `JulianDate` in CesiumJS, and a position, and we add that as a sample:

```javascript
for (let i = 0; i < totalSeconds; i+= timestepInSeconds) {
  const time = Cesium.JulianDate.addSeconds(start, i, new Cesium.JulianDate());
  // ...Get position from satellite-js...
  const position = Cesium.Cartesian3.fromRadians(p.longitude, p.latitude, p.height * 1000);
  positionsOverTime.addSample(time, position);
}
```

Finally, we pass this `positionsOverTime` object to our point.

```javascript
const satellitePoint = viewer.entities.add({
  position: positionsOverTime,
  point: { pixelSize: 5, color: Cesium.Color.RED }
});
```

The point will move as the timeline at the bottom moves. To attach the camera to the moving point we do:

```javascript
viewer.trackedEntity = satellitePoint;
```

## Conclusion

I hope you enjoyed learning a little bit about what goes into building a satellite tracker. There's a lot more to the topic we didn't touch on, like what exactly do the parameters in the TLE mean? How often are they updated? How are they updated?

I don't know, but I find it really empowering to know what formats this kind of data is published in & where to get it, and quite amazing that we can do all this directly in the browser with JavaScript!

Here's a couple fun ideas to explore now that we can do this:

**Visualize multiple satellites**, like the entire Starlink constellation. Inspired by [Celestrak's viewer](https://celestrak.com/cesium/orbit-viz.php?tle=/pub/TLE/catalog.txt&satcat=/pub/satcat.txt&referenceFrame=1) which shows every satellite in its catalog. Perhaps visualize how the number of Starlink satellites grew over time? 

**Or simulate what it would look from street level**. Maybe add buildings/elevation data to find the best place in the city to see the satellite? 

There's a prototype of this in [street-level.html](https://glitch.com/edit/#!/satellite-viewer?path=street-level.html%3A1%3A0) in the Glitch source code. Demo: [https://satellite-viewer.glitch.me/street-level.html](https://satellite-viewer.glitch.me/street-level.html).

![satellite-street](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c1zoskrt1v9287t441wo.gif)
 
See also [James Darpinian's "See a satellite tonight"](https://james.darpinian.com/satellites/) app which uses a combination of CesiumJS and Google street view.

It might also be fun to use a 3D model of the right scale instead of a dot, and get a real sense of how close satellites get to each other in space.