[GSoC2018|Orbitdeterminator|Arya] Week #8-9: Implementing Kalman Filter

After the implementation of the Cowell propagator, it was time for the Kalman Filter. It is an algorithm that uses a series of measurements observed over time, containing statistical noise and other inaccuracies, and produces estimates of unknown variables that tend to be more accurate than those based on a single measurement alone. A Kalman Filter is the ideal filter for a linear process having Gaussian noise. Since our process is not linear, a modified version of the filter must be used. This is called the Extended Kalman Filter. It linearizes about the point in question and applies the regular Kalman Filter to it.

To apply the filter, the following things are required:

  • A function f which outputs the next state, given the current state.
  • The matrix F which is the jacobian of f .
  • The matrix H which converts state into corresponding observations.
  • A matrix Q denoting process noise. It consists of the inaccuracies in the model.
  • A matrix R denoting measurement noise.

The variables involved during the calculations are:

  • x – the state of the system
  • z – the observations of the system
  • P – covariance matrix of x. It denotes the inaccuracy in the estimate. As the filter runs, P is supposed to decrease.
  • K – the Kalman gain. This matrix is between 0 and I and denotes how much the observations can be trusted vs how much the model can be trusted.

The filter consists of two major steps:

  • Predict – in this step, we predict the next state (x) and covariance matrix (P) using our model.
  • Update – in this step, we update our estimates of x and P using observations (z).

The equations of the filter are:



The filter has to be initialized with a value of x and P. If the initial value of P is not known, it can be set to an arbitrary large value.

My implementation

Currently there is a doubt about whether the DGSN will be able to supply complete velocity information at any time. Hence, I left out velocity from the computation and only focused on the position. Therefore x is the position vector. f in this case is rk4(_,t1,t2). To find F I numerically found out the Jacobian of f with a step size of 0.0005 km. I set Q and R as diagonal matrices with constant values of 10 km and 30 km respectively. Since we are directly getting the coordinates from DGSN, h is the identity function and H is the identity matrix H. I initialized P as a diagonal matrix with large values. Then I ran the filter and after it converged it started giving out smoother values.

Graph showing the Kalman Filtered x-coordinate

Possible simplifications

I observed that for short intervals of time, F is approximately equal to the identity matrix. Since computing the Jacobian is expensive, we can set F equal to I for short periods. For long periods since observations are to be trusted more, we can set x to z directly. I also observed that P is approximately equal to a scalar times I. Making these simplifications will transform the matrix equations into scalar ones:



If q and r are constants then we can further simplify the equations to find the final steady state Kalman Gain k and covariance p:

The simplified equations become:



Possible improvements

Of course, the simplifications highlighted above are only possible because our covariance matrices are too simple. Some possible improvements are:

  • Use time varying Q and R matrices. After a long interval of no measurements, the uncertainity in position is higher. So Q should be larger, and vice-versa.
  • Try to include velocity into the state. Right now, we are not sure whether the DGSN can measure all the 3 components of velocity at an instant, but we know that it will be able to measure one component using Doppler shift. Thus we can try to incorporate it into the filter using a time-varying R matrix.


[GSoC2018|OrbitDeterminator|Arya] Week #7-8: Implementing Cowell propagator

Last time when we tried to use the SGP4 propagator in our Kalman Filter, we ran into problems. This was because you can’t stop the algorithm at a certain stage and continue from there. Suppose you want to propagate from to , you can directly propagate from to but you can’t propagate it from to to . So I set out to make our own propagator taking into account two major perturbations – oblateness of Earth and atmospheric drag. Instead of making an analytical propagator like SGP4, I went with a numerical one because that can be stopped and started at any point. So the first challenge was making a numerical integrator.

Numerical Integration

Numerical integration are used to integrate a differential equation from one point to another point. I studied and tested various integrators and finally settled on Runge-Kutta 4th order integrator (RK4).

Advantages of RK4:

  • Good accuracy for small to medium step sizes
  • Low computation cost
  • Easy to implement

Let the initial value problem be and .

Choose a step size and define

for  where,

Note that these equations work equally well whether y is a scalar or a vector. In our case, the differential equation is . It is not in a form suitable to be used directly in RK4. To use it in RK4 we have to vectorize the equation. First, let’s define the state of the system as . Let . Then,

This form can be directly used in the RK4 integrator.

Modelling oblateness of the Earth

Spherical harmonics is used to model the gravity field around the Earth. The method is similar to how Taylor series works. Just like Taylor series, there are various coefficients for each degree of a term in the expansion. The most important coefficient is J2. The next non-zero coefficient J4 is 1000 times smaller than J2. Considering only J2, the gravitational potential around Earth is:

is the radius from the centre
is the latitude of the point with respect to the equator
is a dimensionless constant equal to
is the equatorial radius of the Earth, equal to

To get the acceleration in xyz axes, find the gradient of U and transform coordinates to cartesian. Doing this, we get:

These equations implement J2 perturbation. The main effect of J2 perturbation is precession of the nodes and the argument of perigee.

Adding drag to the propagator

The simplest model for drag force is . We have to determine the air density, relative velocity and drag coefficients. For air density I used the model given on this website and adjusted the constants for the current solar cycle. The final air density model is:

where h is the altitude of the satellite above the surface of the Earth.

To find the relative velocity, one must account for the rotation of the Earth. where .

After putting together all this, I adjusted the drag coefficient so that satellites decay at approximately the current rate of decay. This finishes the implementation of drag perturbation.

Combining both the perturbations we get:
Putting it in the RK4 integrator completes the propagator.

Propagated orbit of the ISS.

Problems faced

I tried the algorithm on a recent TLE of the ISS (shown below).

ISS (ZARYA)             
1 25544U 98067A   18190.66798432  .00001104  00000-0  24004-4 0  9994
2 25544  51.6418 266.6543 0003456 290.0933 212.4518 15.54021918122018

According to this, it makes 15.54021 revolutions a day. This translates to a time period of 5559.8 secs. However, according to other simulators I found online, the actual period is around 5556 secs. A difference of 4 secs might not sound like much, but over the course of a day it adds up. The Wikipedia page of the ISS lists the time period to be 92.49 mins (5549.4 secs) but also states that it makes 15.54 orbits in a day (5559.8 secs). This is a contradiction. I searched for this issue and tried to correct it, but failed. Right now, the only method is to adjust the semi-major axis by a few kilometres to fix the time period. I will try to correct it later.

Future Work

  • Implementing the Kalman Filter
  • Testing against real world satellites


[GSoC2018|OrbitDeterminator|Arya] Week #5-6 – Making a DGSN simulator

This week I worked on propagating the satellite state. The NORAD TLEs are supposed to be used with the SGP4 propagator. It takes care of major perturbations like the oblateness of the Earth and atmospheric drag. Right now, Aakash is working on making one for our organization. But until then, I decided to work with the SGP4 module by Brandon Rhodes.

Working with SGP4

To make the code, I thought in reverse. We wanted to propagate a state vector from one time to another. So our function should look like this:

def propagate_state(r,v,t1,t2)

I checked and saw that the module only accepts TLEs as input. So we have to convert r,v to keplerian elements. Then artificially generate a TLE and input it to the module. The Celestrak website specifically asks us not to do this. But when data is not available, one must make approximations. I followed this approach and made the function successfully. However, I wasn’t satisfied. Artificially generating TLEs (which are strings) is computationally expensive. I wanted a direct way to feed the input. So I studied the source code and made another function that does the job without resorting to strings.

To propagate the state, we need to set the following parameters:

whichconst, afspc_mode,
inclo, nodeo, ecco, argpo, mo, no,
satnum, ndot, nddot, bstar,
epochyr, epochdays, jdsatepoch, epoch

The first line has constants which have default values. whichconst is the gravity model which is wgs72 by default. And afspc_mode is False unless you are dealing with old TLEs.

The second line has paramters that can be set directly from keplerian elements. The elements in order are inclination, longitude of ascending node, eccentricity, argument of periapsis, mean anomaly and number of orbits in a day.

The third line has parameters which are available in TLEs but they are not available to us. I set satnum, ndot and nddot to 0. satnum is irrelevant for the calculation. And ndot and nddot are normally very close to 0. For the bstar term, I couldn’t set it to zero because that would disable drag simulation. So I went through the b* terms of many satellites and got an average value of b* = 0.21109x10-4. Unless a b* term is supplied, this is used for the simulation.

The last line has parameters related to the epoch. These paramters in order are epoch year, day of year, julian date of epoch, and a Python datetime object of the epoch. These can be set by converting the epoch into a datetime object and manipulating it.

After putting together all this, you get the propagator:

The propagator in action.

The DGSN Simulator

Until the real DGSN comes online, we must work with a simulator. A live simulator will also allow us to make live predictions of the satellite. I thought that just running the propagator in an infinite loop will do the job. But it turned out to be more difficult. In a real time infinite loop, I can’t pause to take input. So I can’t control it while it’s running (like stopping or resuming it). Therefore I resorted to multi-threading. The main thread is used for controlling the program and calculations are done on another thread.

First, I made a basic simulator. It just prints the current location of any satellite, given it’s state at some previous time. There is a speed setting for development purposes too. The demo below is printing the position of the ISS every second in cartesian coordinates.

Next step was to add noise and gaps. Gaps are periods when no signal is available from the satellite. Adding noise was very easy. A random value between -15 and 15 kms is added to every component of the position vector. To add randomness in time, the time period between each observation is randomly decided. Implementing gaps was tougher.

I thought about it and deduced that gaps will be caused when the antenna can’t see the satellite. This will be caused by the rotation of the Earth and the revolution of the satellite. So the gaps will be periodic with time. To implement this, I wrote the following algorithm:

omega = <frequency>

p = abs(sin(omega * t))
if (p >= <threshold>):
    # process the output
    # as if the satellite is
    # within range
    # continue as if the satellite
    # is out of range

The figure below shows the working of this algorithm:

By changing omega, one can control the frequency of the gaps. By changing threshold, one can control the duration of the gaps. A lower threshold means smaller gaps and vice-versa.

Putting it all together, I ran the simulator for 2 full orbits and plotted the results on the webapp I made last week. The results look pretty satisfying. 🙂

Next Steps

In the next couple of weeks, I will be tackling one of the hardest things in my proposal – the Kalman Filter. The Kalman Filter will take predictions from SGP4 and observations from real life/DGSN simulator and combine them to give more precise outputs. To do this first we have to synchronize the two streams of data (model and observations). Then we have to make the actual filter and pass the inputs through it. After that, we have to update the state in such a way that the filter can be used again. That’s a lot of work, so I am looking forward to a very exciting week ahead! 🙂

[GSoC2018|OrbitDeterminator|Arya] Week #3-4 – Ground Track and WebApp for OrbitDeterminator

We got important stuff done in weeks 3 and 4. Everybody knows that visuals and graphics are very important for presentation purposes. The most popular library for plotting is matplotlib. It can generate excellent plots but in the end, they are static images. Instead of static images it’s better if some interactive content is presented. So we decided to make a webapp for OrbitDeterminator. A webapp makes it easier for the end-user to run the program. It can also be hosted on a publicly-accessible server, which means that anyone can use it without going through the pain of downloading and installing the program. For making the webapp, my mentor Alexandros suggested the excellent Dash library by Plotly.

Dash by Plotly

Dash is a Python library that can be used to make web applications. The library is built to be hosted on a server and be used by multiple people at once. It is powered by Plotly which produces beautiful, interactive plots in the browser itself. Programming in Dash is simple. First, you have to define the layout of the app in the form of a list of html components. You can style them as you like or add additional parameters. After this, you have to define callback functions. These functions will be triggered whenever the user interacts with the UI (like pressing a button, or typing some text). These functions can be used to change the elements displayed on the app. This makes the app interactive.

Designing the app

For now, OrbitDeterminator’s primary function is to find a good set of orbital elements that match a noisy set of observations. The webapp will be a graphical way to do this. Observations are stored in CSV files in the following format:

# Comments
# here.
# t x y z
<time_1> <x_1> <y_1> <z_1>
<time_2> <x_2> <y_2> <z_2>
<time_n> <x_n> <y_n> <z_n>

So the webapp must have a place to upload csv files. We also want to show the name of the file and the file contents in case the user has uploaded the wrong file. And of course, we have to show the results and plots of the results. After discussing with Alexandros, I settled on this layout:

  • File upload box
  • File name and contents
  • Keplerian elements
  • Spatial plots
  • Ground track
  • Coordinate vs time plot
  • Residuals plot

But the user might not require all this information at once. So all the components must be collapsible, to hide them from view if not required. Making the webapp required lots of googling but was relatively easy. The end result is this:

OrbitDeterminator Webapp
OrbitDeterminator Webapp

All the plots are interactive. You can zoom, pan, and rotate them. You can also save any plot you want as a png file. Most of the plots were straightforward. The most difficult plot was the ground track, whose algorithm I will discuss below.

Plotting the ground track of a satelllite

To plot the ground track, we have to know the position of the satellite with respect to the Earth’s surface. First, I thought that just converting the coordinates into polar form and plotting φ vs θ would do the job. But I forgot that the satellite coordinates were measured with respect to an inertial (fixed) reference frame, whereas the Earth’s surface is rotating. So the actual coordinates will be different. I went through two articles on CelesTrak and Astronomy StackExchange to come up with the algorithm.

NORAD uses True Equator Mean Equinox (TEME) reference frame for its TLEs. In this frame, the origin is at the Earth’s centre. The z-axis points toward the North Pole. The x-axis points to the vernal equinox. And the y-axis completes the right-handed coordinate system.

TEME Coordinate System
TEME Coordinate System

We need to convert coordinates in TEME to an Earth-Centered-Eath-Fixed (ECEF) reference frame. In such a frame, the origin is at the Earth’s centre and the the coordinate axes are fixed to the Earth’s surface (which means that they rotate along with the Earth). Many such frames exist but the standard one is the International Terrestrial Reference Frame (ITRF). In this frame, the z-axis points toward the North Pole, the x-axis points toward 0°E 0°N. And the y-axis completes the coordinate system. Since both TEME and ITRF share a common z-axis, the latitude will not be affected. Only the longitude will be shifted by a certain amount. This amount, known as the Earth Rotation Angle, is the angle between TEME’s x-axis and ITRF’s x-axis. Now I will write down the pseudocode I used to determine this angle.

Let t be the time at which we want to find the Earth’s rotation. Let t_mid be midnight on the same day, so that t - t_mid is the UTC time during the day. Let J2000 be the time Jan 1 2000 12:00 pm UTC(this is the J200 epoch). Then the pseudocode is:

days_since_J2000 = t_mid - J2000
# multiply the above by an appropriate 
# constant to convert the units into days

# centuries since J2000
Tu = days_since_J2000/36525

# effects of precession of the Earth's axis
tg0h = 24110.54841         +
     8640184.812866 * Tu   +
           0.093104 * Tu^2 -
           6.200e-6 * Tu^3

# Greenwich Mean Siderial Time in seconds
tgt = tg0h + 1.00273790935 * (t-t_mid)

# rotation angle in degrees
rotation_angle = (tgt%86400)*360/86400

After getting the rotation angle, we can subtract it from θ to get the actual longitude.

Putting everything together , we get this plot of the ISS for 21st May 2018 18:27:54 UTC. I used this NASA website to generate the NASA ISS plot.

Ground Track of the ISS on 21st May 2018 from 18:27:54 for 2 orbits.
Ground Track of the ISS as generated by OrbitDeterminator
Ground Track of the ISS as generated by NASA.
Ground Track of the ISS as generated by NASA.

As you can see, the results line up pretty well. Note that this is a simplified model and is not super-accurate, but it gives roughly the same results.

Future Work

  • Integrating the SGP4 model into OrbitDeterminator.
  • Making a DGSN simulator (this will be used until the real DGSN starts working).
  • Making a Kalman filter to combine the two sources of data.

[GSoC2018|OrbitDeterminator|Arya] Week #1-2 – Hello World!

Hello World!

I have been selected to work for AerospaceResearch.net under the Google Summer of Code program. Since I am a new contributer here, I think I should give a short introduction first. I am Arya Das, an undergraduate student of computer science at Indian Institute of Technology, Patna. I am highly passionate about space. I am also interested in robots, flight simulators, playing piano and swimming.

In this blog post I am going to describe my experience so far at AerospaceResearch.net and the project I am working on.

„[GSoC2018|OrbitDeterminator|Arya] Week #1-2 – Hello World!“ weiterlesen