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]


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

  1. Hi all,
    Thanks a lot Tim for your very inresting and usefull blog.
    I used this post to start some new trials with my f9p.
    Is there someone to help what’s is wrong with my testcase. I used conf files providing with datas shared by Tim in this post.
    I collect datas several times for a period a least 24h00. My PPP static results and Kinématic static results differ (near 1m). Any suggestion ?


    1. I can’t access to your google sharing. It is asking me for identification.
      Can you tell us more about your test? Where is it? What reference station are you using?

      If you are in France and using RGP station, it is very likely that your differential results are in rgf93. In Grenoble, the difference between rgf93 and ITRF2014@now (used by PPP, right) is about 0.8 m.


      1. Hello Eric,
        I guess share is ok now.
        Yes, I’m in France (Name of station in the picture).
        Do you now the simplest way to go to Lambert93/RGF93 from ITRF2014@now ?


        1. No, I don’t know an easy way to do the conversion (but I will ask. It may be available on IGN website).
          What I’m doing now is a local correction. I use a 24hours log of a nearby base station and I submit it to the RGP online calculation service (Calculs GNSS Réseau en ligne). In the report, you will find station coordinate in both systems. Then, you can calculate a local shift. For GINA station, you may catch logs on Renag server : ftp://renag.resif.fr/data/2021/ (folders names correspond to the day of the year).


          1. @Eric and @Tim : thanks for your answers. I’m near the solution using proj tools but it is not trivial.


          2. It depends on the direction…

            direction ITRF2014@epoch to RGF93 :

            You can install Circe 5.2 which implements this transfo.

            direction RGF93 -> ITRF2014@epoch :

            The easiest way is to go through the online transfo on the EPN website (https://www.epncb.oma.be/_productsservices/coord_trans/index.php). There is a little trick to make it possible. In theory, in order to transform coordinates between two reference frames and two different times, you need velocities for the station. Fortunately, at least if your point has a velocity close to the velocity of the plate, you indicate in input that your points are in ETRF2000@epoch and in output you choose ITRF14@eproch (the same one). In this case, the software will calculate the 7-parameter transfo @epoch from the 14 parameters of definition and apply it.

            It should also be possible to do it under Proj (>=v6) if you need something automatable.


    2. Hi C. The PPP results will be in the WGS84 datum and the RTK/PPK (kinematic) results will be in the datum of your base location which is usually not WGS84, so unless you have converted these to the same datum they will not match. WGS84 coordinates are not tied to the tectonic plates so positions are constantly moving in this datum. You need to be careful to take this into account when making the translation from one to the other. This is the most likely explanation for your discrepancy.


  2. Hi Tim. Would it also still be interesting to look at a realtime PPP positioning with SSR data with the F9P? This could be interesting for stationary dual band receivers that have not a reference station nearby? Or would the results above also apply for this case? In your post of 2018 you are not yet addressing the F9P. Keep up the good work!


    1. Hi Wieger. Yes that could be interesting. I would expect the result to be similar to a post-processed forward-only PPP solution with the ultrarapid precise ephemeris/clock files.


  3. I have a side question regarding precise ephemeris and clock files download. Can we get them (the final at least) through RTKGET? Any tip would be welcome.


    1. Hi Eric. Yes RTKGET can be used to download precise ephemeris/clock files. Instructions are in section 3.9 of the RTKLIB manual. There is a sample URL_LIST.txt file included with the demo5 executables which is used by RTKGET to access the correct data locations. This is fairly out of date at this point, so it will need to be updated with the latest urls.


    1. Hi Anton. I did not do any antenna calibration testing myself. RTKLIB has the option to specify an antenna or use the antenna field in the rinex header to specify an antenna for which the calibration data is looked up in an antenna calibration file pointed to in the file options. I used the igs14.atx antenna calibration file which is included with the demo5 executables. The u-blox antenna is not included in this file but the antennas for most CORS stations are.


      1. Hi Tim, thank you for your comment!
        BTW do you ever think to test quite interesting module supporting L1,L2,L5,L6 signals? I mean Allystar TAU1302. It’s RTKLIB compatible according to the datasheets.


        1. Hi Anton. I did look at a version of the AllyStar TAU1302 about 18 months ago. At the time I felt that it looked quite promising but was not mature enough to be considered as a viable alternative to the u-blox F9P. I was waiting for some firmware updates before I reevaluated it but am not sure the current status of those updates. I should take another look.


        1. Hi Eric,

          I don’t think its good practice to use the calibrations for another ublox antenna. I believe you need to calibrate your antenna and the parameters would be different for such kind of antennas.


          1. May be, there could be sample variation, different supporting disks and also sensitivity to non horizontality. Still, I would be interested in determining the antex file for a static antenna. Above scientists showed that a simple elevation model is enough to get a correct modelization. Adding an angular dependence didn’t improve that much.


        2. Hi Eric. I also see degraded fix rate with that antenna file. I don’t believe it’s an accurate calibration for the ANN-MB-00 based on my results as well as some data I have seen from another source. In many, if not most, cases you should be fine without antenna calibration. Typically, and I believe this is the case for the ANN-MB-00, the horizontal antenna calibration errors will be just a few millimeters. The vertical error may be a centimeter or more but in many applications this component is less critical.


          1. And also, in mountain, I found that elevation values were sensitive to tropospheric delay. With the default Saastamoinen option for ZTD, there was a systematic bias versus elevation of the reference station. Better results for elevation were obtained with the EstimateZTD option but with lower fix. There is also an “Input ZTD” for which I didn’t found any explanation.


          2. Hi Eric. Yes, RTK/PPK solutions are more sensitive to vertical differences in position between base and rover than they are horizontal distances because of the greater tropospheric delay differences, so estimating the tropospheric delays with the “EstimateZTD” option is likely a good choice in this case. When choosing the “nearest” base station it is good to consider difference in elevation between rover and base as well as horizontal distance. The “InputZTD” option to read precise tropospheric delays from a file was previously supported for PPP solutions only, but was removed in the b34 versions of RTKLIB for all solutions. I will remove the option from the RTKPOST option menus in the next code release.


          3. Hi Eric. Unless there is good reason to be different, I like to keep the demo5 version of RTKLIB synchronized to the official 2.4.3 version as much as possible. This makes it easier to port over updates from the official code and provides continuity for users switching between the two. When the InputZTD option was removed from the 2.4.3 version, I did the same in the demo5 code as part of porting over the b34 changes. I will consider putting it back in if there seems to be a demand for it.


          4. I understand. I was mostly wandering what was this undocumented option and if I can take advantage of it. Indeed, if doesn’t exists any more…


  4. 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!


    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!


  5. 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.


    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.


      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.


  6. 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).


    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!


  7. 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.


    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.


  8. 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!


    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.


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 )

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.

%d bloggers like this: