Another look at L1/L5 cellphone PPK with RTKLIB

I last looked at a L1/L5 dual frequency cellphone data set from the Xiaomi Mi8 cell phone about 18 months ago in this post. Recently I had some questions and discussion in the comment section regarding a data set from another Mi8 phone, in this case collected by a group at the Universität der Bundeswehr München and available for download here. I gave a few hints for getting a reasonable PPK solution with RTKLIB in my responses to the comments but thought it deserved a more complete explanation.

The data itself is quite unique. It was collected while the phone was sitting on a choke ring antenna base to reduce multipath as shown in this image from the above link. The choke ring base and phone were then rotated around a pivot to create a circular trajectory.

Universität der Bundeswehr München: Phone on choke ring base

For this analysis I used the latest b34_d3 version of the demo5 RTKLIB code for which the Window executables are available on the rtkexplorer website or the source code is on the b34_dev branch on Github. I did make some recent changes to this code to improve the L1/L5 solutions so you will need to use this code version or something more recent if you are attempting to duplicate my results.

First let’s take a look at the raw observations from the cell phone. The quality looks quite good, there are relatively few cycle slips (red ticks) or missing samples. The blue lines indicate L1 and L5 measurements and the red lines indicate L1-only measurements. The accompanying base data includes matching observations for all of these satellites and frequencies.

Raw observations from Xiaomi Mi8 cell phone

Before running a solution, I did a little pre-processing of the raw observations to reduce the number of codes since RTKLIB sometimes does not handle large numbers of different codes well. Using RTKCONV to convert from rinex format to rinex format, I reduced the number of codes for both base and rover from the original rinex headers below on the left, to the new rinex headers on the right. I used the frequency and code buttons in the Options menu to select the desired frequencies and codes. Not only does this tend to give better results when multiple codes are provided for single frequencies, but it also makes debugging easier if things don’t go well on the initial solution attempt.

[Note for b34_d3 version of RTKCONV: Use the “L3” button to select “L5/E5a” frequencies. I will be updating this in the next release to label it as “L5/E5a” to match the naming convention used in the other demo5 apps]

Base codes before and after RTKCONV rinex->rinex conversion

Rover codes before and after RTKCONV rinex->rinex conversion

Next, I downloaded a multi-GNSS navigation file for the date of this data from CDDIS. There was a navigation file included with the data but it did not seem to use the standard format for the Galileo ephemeris and RTKLIB was not able to extract valid ephemeris for the Galileo satellites from that file.

For a config file I started with the sample PPK config file for the u-blox F9P that is included with the demo5 b34 executables (f9p_ppk.conf). However I made a few changes to optimize for the dataset. Here is a list of the changes I made:

pos1-frequency: l1+l2 -> l1+l2+l5 # enable L5
pos1-snrmask_r: off -> on # enable SNR mask for rover
pos2-snrmask_b: off-> on # enable SNR mask for base
pos1-snrmask_L1: 35,35,… -> 34,34,… # reduce threshold for low SNR
pos2-arlockcnt: 0 -> 5 # improved cycle slip detection
pos2-arminfix: 20 -> 10 # adjust for 1 Hz sample rate
pos2-rejioinno 2 -> 1 # tighten outlier threshold

stats-errphase 0.003 -> 0.006 # adjust for larger residuals
stats-errphaseel 0.003 -> 0.006 # adjust for larger residuals
stats-prnbias 0.0001 -> 0.001 # adjust for larger residual biases

I prefer to run solutions with the SNR mask enabled but don’t leave it on in the default file since the optimal SNR threshold can vary for different receivers. In this case, the SNR values of the rover data are fairly low, presumably due to the relatively low quality antenna in the phone, so I somewhat arbitrarily decreased the minimum SNR threshold to 34 dbHz.

After running an initial solution I also noticed that the solution residuals plotted with RTKPLOT were a fair bit higher than I am used to seeing with u-blox solutions. I’m assuming this is related to the low SNR’s and due to the very small, relatively low quality antenna in the phone. To adjust for this I increased the two terms used to calculate the standard deviations of the carrier phase measurements (errphase, errphaseel) from 0.003 to 0.006. The biases in the carrier phase residuals also looked unusually large so I increased the standard deviation of the carrier phase biases (prnbias) from 0.0001 to 0.001.

I generally set arlockcnt=5 for all receiver types except u-blox, since I find most other receivers allow more unflagged cycle slips than the u-blox receivers do.

The default config file is optimized for a 5 Hz sample rate and this data is 1 Hz so I lowered arminfix from 20 to 10 since it is measured in samples not seconds.

I normally prefer to set the outlier threshold (rejionno) to 1 meter but have recently increased it to 2 meters in the default config file. This is because I have found that 1 meter is occasionally too low and a too low value causes more problems than a too high value. For this experiment, I put it back to 1 meter.

For the most part, these changes are relatively minor optimizations for the specifics of this dataset and are not necessarily related to differences between L1/L2 solutions and L1/L5 solutions. For more information on any of these config parameters see appendix F in the demo5 version of the RTKLIB manual. This is also included with the demo5 executables.

I then ran a combined (forward+backward) PPK solution using RTKPOST. The result was quite decent with a fix rate of 98.2% with relatively small deviations from the expected circular trajectory.

One thing however that was somewhat unusual about this solution is the relatively large differences in the vertical component between the forward and backward solutions. A useful tip with RTKPLOT is that if you plot the “.pos.stat” residual file instead of the “.pos” solution file, you will see both forward and backward solutions before they are combined as shown in the plot below.

Notice that the differences between forward and backward solutions are reasonable for the horizontal components, but quite large, as much as +/-10 cm for the vertical component. Usually large differences between forward and backward are caused by false fixes and appear in all components. I don’t fully understand this result but I don’t believe it is a false fix and suspect that it is due to the large amount of noise in the raw observations. These differences were actually large enough to cause RTKPOST to downgrade some of the fixed forward and backward solution points to float before I increased the “stats-xxx” config parameters and in fact were part of the reason I increased these.

It’s always a little hard to know how well a config file optimized for a single data set will generalize to other similar data sets. To get at least a sanity check for this config file, I ran it on the Mi8 data set from the earlier post. The data was actually static but I ran it as a kinematic solution. The result matched the previous solution position with 100.0% fix rate and reduced the time to first fix by more than a minute from the previous solution, so it passed the sanity check.

Previous Mi8 data set run with the new code and config file

If you do run this code and config file on your own cell phone data, I would be interested to know how it goes and I think other readers would be as well. You can leave a comment below.

One thing to consider when collecting data is the ground plane. I suspect that most people won’t have an extra choke ring antenna base lying around but I would suggest using at least a metal disk under the phone as a ground plane to reduce multipath.