A look at the recently released RTKLIB b34 code

In January 2021, after a fairly long gap of 17 months without code updates, Tomoji Takasu released a b34 update to the 2.4.3 RTKLIB code. He and his team apparently were very busy over that time as, according to Github, the new release has 1064 changed files, 279,767 additions and 312,550 deletions! While some of these numbers come from reorganizing the file structure, this still constitutes a major rewrite of the codebase and a significant challenge to merging the updates into the demo5 version of RTKLIB.

After a fair bit of effort I have completed a first pass at this merge. Given the magnitude of the changes, I have decided to keep this update on a separate branch in the demo5 repository until it is better tested and more stable. It is now on the demo5_b34_dev branch and the beta executables, along with the more stable b33f version, are available here. All of the Windows GUI and CUI apps appear to run as well as all the linux CUI apps. I have done a very limited amount of testing and am not aware of any major issues in any of the apps at the moment but expect that with more testing, issues will be found. Unfortunately the linux GUI Qt apps have not been updated and given the amount of work involved to do this they will most likely be dropped from both the 2.4.3 and the demo5 code.

At this point I am looking for feedback from regular (or new) users of the demo5 version of RTKLIB. In particular, I would like to focus on finding and fixing features or capabilities that are functional in either the demo5 b33 code or the 2.4.3 b34 code but that are not functional in the demo5 b34 code. Ideally, if you find an issue of this sort, if you can send me an email with your data set, config file, and results with both the good code and the bad code, and a detailed description of the problems, it will make it easier for me to track them down. I will also be monitoring issues reported to the demo5 Github repository, so you can use that mechanism as well, it may just be harder to share data that way.

One thing I should mention is that at this point, the Swiftnav, Comnav, and Tersus receivers are not supported by the demo5 b34 code since they are not supported in the 2.4.3 code but I hope to eventually bring these receivers back into the demo5 code. In the meantime you can still convert files from these receivers to RINEX using the b33 code and run post-processed solutions with the b34 code.

In my limited testing I did not find significant differences in the results between the b33 and b34 code but I believe the emphasis of the changes was on basic structural improvements as well as improvements for the newer constellations and signals which for the most part were not included in my testing. I ran a data set collected from a u-blox F9P moving rover with a PPK solution using a CORS reference as base as well as a kinematic PPP solution. The PPK solution was virtually identical between the two codes but the b34 code did run about 30% faster which is a nice improvement. The PPP results were similar but the b34 results were no better and maybe a little worse than the b33 results so there may still be some room for improvement there. I do think that many of the structural changes will be valuable in the long term even if they do not have an immediate payoff.

There are also some new features in the code that I am looking forward to exploring after I’m more comfortable that the basic functionality is there.

So give it a try and let me know what you find!

I will try to update the beta source code and executables fairly frequently as I make fixes and will eventually move them to the main branch of Github but will probably keep the b33 code around on a separate branch for the foreseeable future.

Exploring kinematic single-receiver solutions with RTKLIB and the u-blox F9P.

Most of my work with RTKLIB has been done with differential solutions (RTK or PPK) using two receivers, a base and a rover. I have briefly explored static PPP solutions but have not previously looked at kinematic PPP solutions or analyzed the internal standard precision solutions of the u-blox F9P receiver. In this post, I will take a closer look at these options.

For this experiment I started by collecting a data set using an F9P receiver connected to a u-blox ANN-MB-00 antenna mounted on the roof of my car. The first 40 minutes were static followed by another 30 minutes of driving around residential and light industrial neighborhoods, all sparsely treed. The goal here was to start with something not overly challenging so I intentionally avoided any significant tree canopy, underpasses, or tall buildings. I enabled and logged u-blox raw observation and navigation messages (RXM-RAWX and RXM-SFRBX) as well as NMEA solution messages for the internal F9P standard precision solution ($GNGGA, $GNGLL, $GNGST).

I’ve uploaded this logged raw data file as well as all other necessary files to generate the solutions described below to the download section of my website in case anyone wants to download the data and duplicate my results. I did make some changes to the RTKLIB code as I worked through the experiment, so you will also want to download the executables for the latest b33f version of the demo5 RTKLIB code.

To generate a ground truth for the subsequent comparisons, I first converted the raw observation data file to rinex using RTKCONV and then ran a combined-mode PPK solution of the raw data against a nearby CORS station using the demo5 b33f version of RTKPOST and my standard configuration settings for the F9P (ppk.conf in the data download folder). The fix rate in the resulting solution is 99.9% and the CORS station is less than 10 km away, so I have a relatively high confidence in the accuracy of this solution. Note that the base coordinates in the CORS station rinex header are NAD83 while the single receiver solutions will all use the WGS84 datum. To correct for this, I have manually specified the base position in the config file options in WGS84 coordinates adjusted for the date of the data set.

In the image below, the raw observations are on the left, and the ground track of the PPK solution is on the right. The transition in the raw observations where the cycle slips (red ticks) begin indicate when the car started moving.

Raw observations on the left and RTKLIB PPK solution on the right

Since the raw log file from the F9P receiver includes NMEA position messages in addition to the raw observations, I can plot this file as a solution file directly with RTKPLOT. This will extract the NMEA positions from the file and ignore the raw observations.

If I use RTKPLOT to plot both the RTKLIB PPK solution and the F9P real-time (NMEA) solutions, I can then select the “1-2” button to plot the difference between the two solutions. Since the errors in the real-time solution will be much greater than the PPK solution, we can take this plot to indicate the error in the real-time solution.

Difference between RTKLIB PPK solution and F9P real-time standard precision solution

Note that the errors are larger (and lower frequency) during the static portion of the data set on the left half of the plot than they are on the right when the car is moving. This may seem counter-intuitive but it is because the multipath component of the error gets randomized by the movement of the receiver antenna relative to the satellite signals.

If you look at the datasheet for the F9P, you will see that horizontal accuracy is specified as 1.5 m CEP accuracy for a PVT solution and 1.0 m CEP accuracy for an SBAS solution. I didn’t calculate the exact CEP value for the plot above, but it would correspond to where the square root of the sum of the squares of the two horizontal components was less than the spec for 50% of the time. In this case the solution included SBAS augmentation so I would expect the 1.0 m accuracy spec to apply. Just eyeballing the plot, it looks we are getting at least this accuracy during the static portion and even better accuracy during the dynamic portion. This makes sense since the spec is for a static case. I could not find an F9P spec for dynamic accuracy.

Note that I have upgraded the firmware in the F9P module to version 1.13 which was released fairly recently. SBAS support was added to the F9P with the 1.13 upgrade so I suspect if you are running older firmware on your F9P, you may see larger errors. You can check the firmware version running on your module by querying the UBX-MON-VER message from u-center.

Sometimes it’s hard, at least for me, to look at the raw error numbers and visualize what they mean in the real world, so I have shown a snapshot of the two ground tracks below, the green dots are the PPK solution, and the yellow dots are the real-time F9P solution. I suspect for many applications, the level of error in the real-time solution would be acceptable. I was actually surprised to see how good it is.

Ground track of RTKLIB PPK solution (green dots) and F9P real-time solution (yellow dots)

So next, let’s look at the RTKLIB single frequency post-processing solutions. I will start with the “Single” positioning mode solution. This mode gives a very coarse solution and is really only suitable for initial approximate locations for the other solution types but we’ll take a quick look at it anyways. I ran an RTKLIB solution using the same config file as for the PPK solution, I just changed the “Positioning Mode” option from “Kinematic” to “Single. I then plotted the difference between this solution and my reference solution as I did before. Below is a plot of the difference between the two solutions.

Difference between RTKLIB PPK solution and RTKLIB single solution

As you can see, the errors are much larger than the real-time F9P solution and so of very little use.

Next, let’s look at the RTKLIB Kinematic PPP solution to see if there is any opportunity to improve upon the real-time solution here. To create a kinematic PPP solution I used the raw observation and navigation file from the F9P, along with precise ephemeris and clock files, a recent DCB (differential code bias) file, an antenna calibration file, and the ppp.conf config file, all included in the uploaded data set folder. I used very similar configuration settings to the PPK solution with a few exceptions. First, I enabled or configured all the PPP relevant parameters. For now, you can see the details of these settings in the ppp.conf configuration file, I hope to cover them in more detail in a future post. Next, I increased the minimum elevation mask from 15 degrees to 20 degrees based on earlier experiences showing that the RTKLIB PPP solutions are more vulnerable to errors and cycle slips in the low elevation satellites than are the PPK solutions. Also, I increased the outlier threshold from 1 meter to 30 meters since the residuals are much larger in the PPP solution and the outlier handling is different. I then ran two solutions, one with the first rapid precise ephemeris/clock files I was able to find online published after the data was collected (SHAOMGXRAP*.*), and the second solution was run with the final precise ephemeris/clock files (ESAOMGNFIN*.*). Both solutions were run with the most recent DCB files I was able to find which were based on analysis from Nov 2020. There are a number of online repositories of precise ephemeris data but many of these are GPS and GLONASS only, it is more difficult to find precise files that include Galileo and Beidou as well. The CDDIS, ESA, IGS, CODE, and other websites all have different variations of precise ephemeris files available for download but I have not yet found any one site that is best for both rapid and final multi-constellation ephemeris files.

Below are the differences between the two PPP solutions and the same PPK reference solution as used above, with the rapid ephemeris solution on the top, and the final ephemeris solution below.

Difference between RTKLIB PPK solution and RTKLIB PPP solutions. Top solution used rapid precise ephemeris/clock files, Bottom solution used final precise ephemeris/clock files.

In this case the rapid ephemeris/clock files were available the following day, the final ephemeris/clock files were not available until a week after the data was collected. Both solutions show smaller errors than the F9P real-time solution and the errors in the final solution are smaller than in the rapid solution, as would be expected.

PPP solutions typically have long convergence times, so some readers might be asking themselves why they don’t see any signs of this in these PPP solutions. The answer is because they were run in “combined” solution mode meaning the solution is run forwards and backwards and the two combined. Typically in a combined solution, and this includes the 2.4.3 version of RTKLIB, the kalman filter states are reset between the forward pass and the backwards pass to insure the two solutions are independent. In the demo5 code I have chosen not to reset the filter states unless it is a PPK solution with fix-and-hold enabled. This means the filter states will be fully converged at the beginning of the backwards pass and this will improve the overall accuracy of the solution at the possible expense of some theoretical loss of integrity in the solution, although I have not found this to be an issue in my limited testing. The convergence still needs time to occur however, so I would not recommend using this technique on data sets less than half an hour and even that might be marginal.

As you might expect, the errors in the PPP solutions are a fair bit lower than the real-time solution, while still quite a bit larger than the PPK errors. I suspect there are situations where these solutions would be of use, particularly where local CORS stations are not available. The biggest caveat is that the PPP solutions are less robust than either of the other two solution types and it is also more difficult to detect larger errors in the PPP solutions compared to the PPK solutions since there is no verification step from the ambiguity resolution.

Use of static PPP solutions seem to be quite common, I see less use of kinematic PPP solutions, so I was somewhat surprised and pleased to see how well the RTKLIB kinematic PPP solutions did work.

For static PPP solutions I prefer to use the free online CSRS PPP solution service I’ve described in other posts rather than RTKLIB since it is simpler to just submit the observation file than it is to find the precise ephemeris file and I also have more confidence in the accuracy estimates of the CSRS solution. It does take longer to converge than the RTKLIB solution since it is only using GPS and GLONASS satellites but this is only a minor inconvenience for a static measurement. For a kinematic measurement, the smaller number of satellites is a bigger problem but I thought it was worth a shot so I submitted the raw data file to CSRS and specified a kinematic solution. The CSRS solution is not directly plottable with RTKPLOT but I wrote a short python script to convert it to RTKLIB solution format and plotted the difference from the reference solution below.

Difference between RTKLIB PPK solution and CSRS kinematic PPP solution

The solution is excellent while the car is stationary but not much better than the real-time F9P solution when the car is moving, so this is also probably not a useful solution for a moving rover. It is interesting that in this case, unlike all the other solutions, the errors are larger when the car is moving than when it is stationary. This is probably because of the increased number of cycle slips during this time.

While that is probably enough for an initial exploration, I hope to take a closer look at some of these results as well as potential improvements in future posts. Please comment below if you would like to add anything else to the discussion.

[Updated 1/16/21 to correct for an error in the original translation of the CORS station coordinates to WGS84 coordinates for the date of the data set]