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]

12 thoughts on “Exploring kinematic single-receiver solutions with RTKLIB and the u-blox F9P.”

  1. Hi Tim,
    Very interesting and informative. Thank you.
    Quick clarification – in your first plot the caption on reads “Difference between RTKLIB PPK solution and F9P real-time RTK solution”- but I think you meant “F9P real-time solution” (no RTK) since I don’t think you used RTK at all in this case right?

    Also, on the last plot, the CSRS kinematic solution diff to the PPK is about 0.5 [m] and definitely less than 1 [m], yet you mention it is the same as the real-time F9P solution. But it seems the PPK to F9P diff last plot shows larger than 1[m] throughout.

    Again, great blog!

    Like

    1. Hi Gideon. Yes, both your points are correct and I have updated the post to fix the errors. I made the comment about the CSRS solution in an earlier version of the post in which I had a datum error that made these two solutions much more similar but missed updating it when I corrected the error. Thanks for catching these!

      Like

  2. First, thanks for doing this blog and the demo5 fork – very enlightening.

    A cute trick to get a NAD to WGS translation is to run a static data set through OPUS and look at the difference between the NAD and ITRF results. Also the OPUS ITRF solution compares well to the CSRS-PPP solution with the ITRF coordinate option but not with the NAD option which also uses epoch of data.

    In other news, there may be a small issue with demo5b33f on Linux/gcc. In pntpos.c there is a change to the test at the top of the prange() function, adding ||P1==0.0 to the predicate but P1 is not defined. Your Windows binaries work fine so maybe this is a difference in the way the compilers allocate local variables? The test always fails with the gcc version of rnx2rtkp and the position solution fails with “lack of valid sats” in the -x 9 diagnostics file. Setting P1=obs->P[0] before the test is a quick fix but maybe not the right thing.

    Like

    1. Hi Bob. Thanks for the tips and the reported issue with the linux code. I don’t use the linux code much myself so am not as aware of it’s status. I will try to take a look at this in the near future.

      Like

      1. Hi Bob. I just checked in a fix for the issue you saw with the linux code. As you said, P1 wasn’t initialized yet. The right answer was to use obs->P[0] which is initialized at that point.

        Like

  3. I noticed there are decimeter level biases in the “static” part of the PPP solutions and it seems to be a datum issue. It looks like the CORS station coordinates in the PPK.conf file correspond to IGS08 @ epoch 2005 while the PPP coordinates would be in IGS14 at the epoch of the precise orbit and clock products (~2021).

    Like

    1. Hi Brian. Good catch! You are correct, I was sloppy with the datum translation for the CORS station. With a more careful translation, I see the North-South bias disappear entirely, and the East-West bias reduced significantly. That should make a good topic for a follow-on post. In the meantime I’ve flagged the error in the post. Thanks for pointing this out!

      Like

  4. I made a similar study while driving on the former “Nationale” road between Grenoble and Lyon (100 km), in France. We have SBAS (EGNOS) and there is a CORS station nearly at the middle of the trip. I made comparison between RTKLib kinematic post-processing, NMEA sequences and online NRCAN kinematic PPP.
    RTKLib : 7164 points with solutions; Q=1 5829 pts, Q=2 1276 pts, Q=5 59 pts.
    NMEA : 7224 points; All points with former Q=1 solution have also a solution in NMEA.
    PPP : 6773 points; Only 5167 points with former Q=1 solution have a solution with PPP.
    For all Q=1 points:
    – average distance between NMEA and RTKlib : 0.95 m.
    – average distance between PPP and RTKlib : 1.18 m.

    So NMEA is better than the online PPP, which also perform poorly under tree cover, in opposite to NMEA which visually is better than RTKLib under trees.

    Like

    1. Hi Eric. Interesting! Your RTKLIB PPP accuracy errors are a fair bit larger than what I saw. This is likely because as you mention, your data was collected under trees. I do see the PPP solutions as not being particularly robust to more challenging conditions.

      Like

  5. Dear Tim, I am so glad you still find time to post to this great blog! I really appreciate it! I have an off-topic question though – does RTKLIB have a maximum altitude/elevation and speed limit as is normally required by the CoCom regulation (18 km and 1000 knots) for hardware receivers? I assume there may be no such limit in RTKLIB since it is actually a software-defined GPS receiver. I just wonder how this is currently implemented and where exactly is the altitude/elevation code in RTKLIB exactly and if you maybe can say few words about it. There is a growing demand in high-altitude ballooning and amateur rocketry for such applications. Thanks!

    Like

    1. To be clearer I thought about using the “single” solution from raw GNSS observations – only with a “rover” (much rather a balloon or rocket 🙂 without any differential solutions.

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.