Evaluating the quality of moving position solutions

Stationary position solutions are easy to evaluate, an ideal solution is a single point, any deviation from this point must be error. It is possible that the point itself is in error in an absolute sense but since I am only interested in the distance between receivers I am not concerned with absolute error.

Evaluating a solution for a moving receiver is more difficult since it is not easy to know what is the correct solution to compare to. If I had an expensive high precision receiver/antenna I could mount it on the same platform as the test receiver and use that as a reference, but I don’t have that, so I need to come up with another solution.

Mounting two low cost receivers on the moving platform gives us a couple of options for evaluation. First of all, since I have data from two nearby base stations, I can calculate two solutions, each using one of the low cost moving receivers as rover, and one of the COREX base stations for base data. Since the two low cost receivers remain a fixed distance apart, the difference between the two solutions should be constant in magnitude. The direction of the difference however is varying as the orientation changes. This means the difference between the two solutions should be a circle with radius equal to the distance. Since each solution is based on different base stations, and different receivers they should be fairly independent. The fact that one of the base stations is reporting GPS only, and the other is reporting GPS and GLONASS should also increase the independence of the solutions, since they are also using different sets of satellites,at least if we include GLONASS satellites in the solution. This not the case at the moment since the default config has GLONASS turned off, but we will turn it on soon.

RTKPLOT has a nice feature which allows us to plot the difference between two solutions. Click on the “1-2” button on the menu bar to see this, after opening both solutions from the file menu.

Below are the two solutions plotted on top of each other. Since the scale is 20 meters per division, it is impossible to see any difference between them.


Here is a plot of the difference between the two solutions while the car was stationary and driving in low velocity circles. Now the scale is 10 cm per division. We can see the expected circle, and the error from that circle is generally less than 5 cm once the solution converges to a fixed solution. I’ve jumped ahead a bit, since the solution in the plots includes some input parameter changes as well as some code changes, so the default solution we just calculated won’t be as clean, but we’ll get to those changes soon. The good news is that it looks like we are getting errors of only a few centimeters after the solution has converged, even when both receivers are moving.


There is also another, simpler way to evaluate the quality of the solutions if, as in my case, we are interested only in relative error and not absolute error. Instead of generating absolute solutions using the COREX base stations, we can calculate a differential solution directly, using one of the low-cost receivers as the base and the other as the rover. The result will then be the position difference between the two receivers. Since the distance between the two receivers is fixed but the orientation between them is varying as the platform they are on is moving, the solution should be a circle, again with radius equal to the distance between the two receivers. In this case, unlike the previous one, we do not get any absolute position information, only the distance between the two receivers. Note that even though the base is moving in this case, we do not use the “Moving-Base” solution option from the positioning mode choices, but stick with “Kinematic”. The solution algorithm does not need exact position of the base, only approximate location. We use the approximate starting location of the base receiver which is in the header of the RINEX observation file and ignore the fact that the base is moving. As long as we are concerned only with relative position between the two receivers and the base does not move a large distance from its starting point, this is a valid assumption to make.

Here is a plot of the result from using both base and rover on the moving platform. Note that it is very similar to the previous plot, generated from the difference between two absolute solutions, and again the error is only a few centimeters once the solution has converged.  As in the last plot, it does include some changes from the default input parameters and code that we will get to later.


This is the form of solution I will use for evaluating the effectiveness of various input parameters and algorithm changes.


Kinematic solution with RNX2RTKP


In the last post we used the RTKPOST GUI to generate a kinematic position solution using “ebay.obs” for the rover data and “zdv13480.15o” for the base station data. To do the equivalent run from RNX2RTKP we use the following command line, run from the folder containing the data files.

rnx2rtkp -k test1.conf -o out.pos ebay.obs zdv13480.15o ebay.nav

The -k option is to specify the configuration file, the -o option is the output file. In this case we use the configuration file we saved from RTKPOST in the previous post.

To plot the calculated solution use the following command line:

rtkplot out.pos

This calls up the same plot GUI we previously accessed by clicking on the “Plot” button in the RTKPOST GUI.

The plots should be identical between this solution and the previous solution generated with RTKPOST.

To keep track of changes between various runs with the same data, I use a matlab wrapper to create a sub-folder, copy the config file, trace file, stats file, and result file to that sub-folder, input a short description of the run, and also save that to the sub-folder.

Kinematic solution with RTKPOST

Kinematic solution with RTKPOST

RTKPOST is the GUI tool in RTKLIB to calculate position solutions. Most of the time I find the CUI version (RNX2RTKP) better fits my needs, but just to check everything is working, it is probably easier to use RTKPOST the first time.

For a demonstration of using RTKPOST to find a kinematic position solution, I will use the ZDV1 (COREX station data) for base station data and the “EBAY” (Ublox M8N receiver data) for rover data. Zipped versions of this data is available here.  Since the exact location of ZDV1 is known and is in the observation file header, the kinematic solution will give us an absolute position by solving for the relative distance between the rover and the base, and then adding that to the base location. I set up the GUI inputs as shown below to point the program to the correct observation and navigation files. If you use my data, be sure to change the paths to match where you saved the data to.


For this first run, to keep things as simple as possible, we will make just two changes from the default setup. Use the “Options” button to get to the options menu. Under the “Setting 1” tab, change “Positioning mode” from “Single” to “Kinematic”. This will give us a differential solution using carrier phase info instead of an absolute solution using only pseudorange. Next, under the “Positions” tab, change the first field under “Base Station” from “Lat/Lon/Height” to RINEX Header Position. This will tell RTKPOST to get the base station location from the header of the observation file.

While you are in the options menu, click the “Save” button, and save the options setup to a location you will remember later. We will use this file as the configuration input file for the CUI version. Then click OK to exit the Options menu.

Click the “Execute” button to calculate position and then “Plot” to see the solution. Select “Gnd Trk” and zoom in and it should look like this. The two rectangles are parking lots. The yellow represents a float solution, the green a fixed solution. The fact that we were able to get a fixed solution at least part of the time is a sign things are working reasonably well.



Zooming into the initial time period when the car was stationary we see the plot below. Since the receiver is not moving during this time, any movement in the solution represents error. During the initial convergence of the kalman filter we see quite a lot of error, but once it does converge, we do get a fixed solution for 5 of the 20 minutes which appears as green in the plot below. During this time you can see the error is roughly +/- 1cm in the xy direction which again is a good sign things are working.


Note about restoring RTKPOST default options:  There is no button in RTKPOST to reset the options to defaults and it remembers the options from the previous session when restarted, so there is no obvious way to put it back to defaults.  The best way I have found to do this is to delete the “rtkpost.ini” file saved in the rtklib\bin folder before starting RTKPOST.


My reference raw GPS data set

In many of the posts to follow I hope to compare the effectiveness of different input options and algorithm modifications to the position solution. To do this it will be useful to have a single representative set of raw data to use for comparisons as well as a set of metrics to compare. I will discuss the metrics in a future post, here I put together a data set that I believe will be a good test, at least for my goals.

My interest is in relatively low velocity scenarios with open skies (I’ll talk more about the details in another post). To generate my reference data sets, I mounted two Ublox M8N receivers with basic patch antennas on top of a car, both antennas sitting on the metal roof for a decent ground plane. I then collected about twenty minutes of data with the car stationary, followed by driving around in circles in a open parking lot (no nearby trees or buildings) for roughly another twenty minutes, followed by another twenty minutes of driving on open roads with some nearby trees at higher speeds. The low velocity circles are what I am most interested in, but collected the other data to allow additional comparisons under different conditions.

I am fortunate enough to have two CORS base stations within 10 km of where I collected my data and so I downloaded the data for both stations (ZDV1 and TMGO) for the same time period. ZDV1 is GPS data only, TMGO includes both GPS and GLONASS data.  That gives me four sets of data. Two are from Ublox M8N receivers with patch antennas, both moving but staying a constant distance from each other, both containing only single frequency (L1) data. The other two are dual-frequency (L1 and L2), fixed position. For consistency, I will use this set of data when comparing results in most of my future posts. Here is a link to a zipped copy of all four data sets in case they may be of use to anybody else. In the file names, “cga” refers to data from the M8N receiver bought from CGA, and “ebay” refers to data from the M8N receiver bought on Ebay.

Base station data for RTKLIB

RTKLIB has a number of different algorithms it can use to calculate position. The first two in the list are “Single” and “DGPS”. Both of these methods use only the pseudorange data and not the carrier phase information. Without the carrier phase information, the precision of these methods is quite low, and probably worse than what the GPS receiver would provide without RTKLIB. In general they are not very interesting other than possibly for some initial debug during setup. The rest of the algorithms can be divided into two groups, differential and PPP. The differential algorithms determine position relative to a known nearby location while the PPP (precise point positioning) algorithms determine absolute position. In general, the data quality of the low cost hardware we are using is only good enough to use with the differential methods and not the PPP methods, so we will focus on those. RTKLIB supports four differential modes: Static, Kinematic, Moving-Base, and Fixed. The kinematic mode is designed to calculate the relative position between a fixed base and a moving rover and that is what we will use. It can also be used with a moving base if we are concerned only with finding the distance between the two and not trying to translate that to a fixed position, and we will use it this way as well.

In my particular application, I am interested only in the distance between two receivers and not in any absolute locations. In this case I will collect data from two low-cost receivers and use one as the base and the other as the rover. In many cases, though, it is useful to use an existing ground station as the base and the low cost receiver as the rover. I use this method for testing and verification, even though I don’t plan to use it in my final solution.

In the US, base data is available online for free from many GPS stations in the CORS network.  Here’s a map of CORS station locations.


For the ground stations I have used, it is generally available online less than an hour after it is taken. It is fairy easy to pull it manually using a user-friendly form from the CORS webpage, or you can use the RTKGET utility in RTKLIB. Be aware that some stations have only GPS data, and some have both GPS and GLONASS data.

Converting raw receiver data to standard text format using CONVBIN

At this point, we have collected raw receiver output data in a Ublox binary format file. We need to convert this to RINEX format before RTKLIB will process it. RINEX (Receiver Independent Exchange Format) is a standard text data format supported by many receivers and post‐processing analysis software.

This can be done with RTKCONV, the GUI conversion program in RTKLIB, but I prefer to use CONVBIN, the CUI version.  To avoid having to remember the exact command format, I automate the call with a simple matlab wrapper. To convert binary data from a file named “testdata.ubx” the format of the command to CONVBIN will look like this:

convbin -od -os -oi -ot -f 1 -ts 2015/12/14 17:25:00 -te 2015/12/14 18:25:00 testdata.ubx

The command line options are all well documented in the RTKLIB user manual and there are many I am not using but here’s a brief explanation of the options I am using:

-od: include doppler frequency data in observation output file

-os: include SNR data in observation output file

-oi: include iono correction in nav output file header

-ot: include time correction in nav output file header

-f 1: one frequency (L1 only)

-ts: start time

-te: end time

I leave off the start time and end time options when I choose to process the complete file.

The output of this command will be a set of text files. The .obs file contains the observation data which in this case is the pseudorange, carrier phase, doppler, and SNR from each satellite for each epoch. The start of this file should look something like this:


The top section is the file header, followed by a timedate stamp and list of satellites for the first epoch. After that is a list of pseudorange, carrier phase, doppler, and SNR numbers for five satellites from the first epoch, each row being one satellite. See here for a complete description for the RINEX observation file data format.

There will also be a .nav file containing navigation data for the GPS satellites, a .gnav file for GLONASS navigation data, a .hnav file for geosynchronous satellite navigation data and a .sbs file containing SBAS correction data. All of these files are in text format and we will need most, if not all of them, as inputs to generate position data.

The data is now in a standard format that can be processed by RTKLIB to determine position.

Collecting raw Ublox data with RTKLIB

At this point, we have verified that the GPS hardware and the link to the PC are working properly, and are ready to start using RTKLIB to collect the raw GPS data.

Start by downloading RTKLIB from Github.  Choose either the main branch ( which is the stable branch or 2.4.3 which is a beta branch (currently at revision b8).

[Update 8/22/18:  Your other choice is my demo5 version of the code which has been optimized for low-cost receivers, especially the u-blox receivers.  This is the code I would recommend now.  The source code is available here and Windows executables are available here]

Recent check-ins have all been made on the beta branch, but most or all of the recent work appears to be unrelated to Ublox receivers or the  kinematic mode normally used for low cost receivers, so the functionality of the code should be nearly identical regardless of which branch you choose.  I am using 2.4.3 because I think it may be easier to merge in any future changes.  The repository includes all the executables, so there is no need to build any code before running it.

There are actually two sets of executables built from the same codebase, a GUI set, and a CUI set.  I am using a combination of the two.  For collecting the raw data, and plotting the results, I use the GUI versions (STRSVR and RTKPLOT) for convenience.  For converting the raw data to RTCM and processing the data, I use the CUI versions (CONVBIN and RTKPOST).  This is for two reasons.  First, because making any changes to the GUI versions requires  an Embarcadero/Borland VCL compiler to re-build the code and this compiler is not available for free.  The CUI versions can be rebuilt with the Microsoft Visual C++ compiler, which is free and one I am also much more familiar with.  The second reason I use the CUIs is because I have found it easier to keep track of all the input and output files for each run when using the CUIs.  I use simple Matlab wrappers to call the CUIs which save all the configuration, input, and output information to a separate folder.  Python wrappers would probably work just as well and it is available for free, but I already have Matlab and again am more familiar with it.

Once RTKLIB is downloaded, and the GPS receiver is connected to your PC via USB, start the STRSVR program (strsvr.exe).  It can be found in the rtklib\bin folder.  Set the Input Stream to “Serial” and use the “Opt” button to set the port and baudrate to match your GPS receiver.  If you’ve been communicating to the receiver with U-center or any other application, make sure you have disconnected them from the com ports to avoid any conflicts.

Next we need to configure the receiver to output the raw GPS signals, pseudorange, carrier phase, doppler, and SNR.   For the Neo-M8N receiver this requires us to use the normally undocumented commands TRK-MEAS and TRK-SFRBX.  First select the “Cmd” button for the input stream and copy the following commands into the “Commands at startup” window.

!UBX CFG-GNSS 0 32 32 1 0 10 32 0 1
!UBX CFG-GNSS 0 32 32 1 6 8 16 0 1
!UBX CFG-MSG 3 15 0 1 0 1 0 0
!UBX CFG-MSG 3 16 0 1 0 1 0 0
!UBX CFG-MSG 1 32 0 1 0 1 0 0

Make sure the “Commands at startup” box is selected, then click the “Save” button to save.  STRSVR will send these commands to the receiver at startup.  The third and fourth commands in this list enable the TRK-MEAS and TRK-SFRBX commands to the USB and UART ports.  See pages 9-13 of this document for an explanation of exactly what these numbers do.  The first and second commands are documented commands to configure how many channels the receiver should allocate to GPS and GLONASS.  Details are explained in the M8 Receiver Description under the CFG-GNSS command.  We could have configured these with the Ublox eval software in the last post, but I thought it was easier to do it all in one place, and also more consistent with other references.


Next, set Output (1) to “File” and use the “Opt” button to select a file name to save the data to.  I use a “.ubx” extension for this raw binary data.  Finally, click on “Start” to start collecting receiver data.  If everything is working properly, it should look something like this:


In the next post we will use RTKLIB to convert this Ublox proprietary binary data to a more friendly text version.