RTKLIB: Tips for using a CORS station as base

Generally, you will get the best RTK/PPK solutions using two matched receivers, one as rover, and one as a local base. This will give you the shortest baseline, maximum number of usable constellations, and no issues with the Glonass hardware biases that come from using unmatched receivers. To do this, you will first need to determine the precise location of your local base receiver. Running a post-processed kinematic (PPK) solution with your own local base receiver as rover and a nearby Continuous Operating Reference Station (CORS) as base is usually the easiest way to do this.

Of course, If you have a CORS station near enough, it is also possible to run solutions directly from your rover to the CORS base but I generally opt for the local base since the solutions will be more accurate and more robust.

For the most part, running a PPK solution against a CORS base is very similar to the local base solutions I describe in many of my other posts but there are a few important differences that I will try to go over in this post. I will describe using CORS stations inside the U.S. with u-blox receivers and the demo5 RTKLIB code since this is what I am most familiar with, but most of the information in this post will apply to other receivers as well as to CORS stations in other countries.

The first step is to find a nearby CORS station. In the U.S, the easiest way to do this is to go to the NOAA User Friendly CORS website and click on the CORS Map link. Zoom into your local area and if you are lucky you should see something like the screen shot below with several nearby choices. If you’re not so lucky you may find only one or two distant choices.

Notice that some stations are GPS only and some are labelled as “GNSS” which indicates that they support multiple constellations, usually GPS and Glonass, but sometimes Galileo as well. Ideally you will find a “GNSS” site within 20 kilometers but if not you may be successful generating fixed solutions with stations up to 100 km or more away. If you are using a dual frequency receiver and solving for a stationary rover you can usually use more distant stations. Be aware that longer baselines will increase accuracy errors so it is a good idea to run longer solutions and average the results from multiple stations if possible. Station sample rate is less important than distance and number of constellations but a higher sample rate is always better if you have the choice.

In addition to the NOAA CORS website, it is also worth checking if your state has its own network of reference stations. These are usually run by the state department of transportation and their data is normally free although you may need to sign up to access it. There is a slightly dated state by state list available here.

If your base station is sampling slower than a 1 second rate then I often find that interpolating between base observation samples improves the solution. In RTKPOST, this is done by setting enabling “Time Interpolation of Base Station Data” in the “Misc” tab of the options menu. In RNX2RTKP, this is done by setting “misc-timeinterp =on” in the config file.

Assuming your rover is stationary, you can run the solution mode as “Static” and this will improve your chance of getting a fixed solution. I prefer to run a “Kinematic” solution if possible, however, since the variation in solution position over time gives some insight into what the accuracy of the solution is, especially if you have collected several hours of observation data.

Since the receiver manufacturers will almost always be different between base and rover in these solutions, you will usually need to deal with the Glonass hardware biases. I describe the different options to account for these in detail in this post so I would recommend reading or reviewing that post for the details. To quickly summarize though, the simplest option is to set “Integer Ambiguity Resolution” in the options menu for Glonass to “Fix-and-Hold” in which case RTKLIB attempts to calibrate the biases. In some cases, this will be good enough to get a fixed solution. However, you are more likely to get a fixed solution if you account for the biases directly. You can do this by setting Glonass ambiguity resolution to the poorly named “Autocal” option and then specifying the biases directly with the “GLO HW bias” option in RTKPOST or “pos2-arthres2” option in the config file. Again, see the above post for the details on how to determine the correct biases and the table of biases by receiver manufacturer. Note that these ambiguity resolution options are only available in the demo5 version of the RTKLIB code.

If the HW biases for both base and rover are close to zero then it’s generally OK to ignore them and set Glonass ambiguity resolution to “on”. This will not be true for a u-blox M8T rover since it’s bias is not zero. However the u-blox F9P bias is close to zero so if you are using it as rover and your base receiver is manufactured by Trimble, Septentrio, Topcon, Spectra Physics, Javad, or Ashtec then go ahead and set Glonass ambiguity resolution to “on”. The receiver manufacturer will be listed in the header of the base rinex file.

The base rinex file header also includes the base antenna type and it’s precise location. Note that the precise location is in the header field labelled “APPROX POSITION XYZ”. Setting the base station position in the options menu to “RINEX Header Position” will cause this value to be used in the solution. If you would also like to include the base station antenna calibration in the solution, then check the “Antenna Type” box and specify “*” in the box below. This tells RTKLIB to use the antenna type listed in the rinex header. You will also need to point to an antenna calibration file in the “Files” tab of the options menu. You can use the “igs14.atx” file that is included with the demo5 binaries for this.

It is important to keep in mind that the accuracy of the solution is going to get worse as the distance to the base station increases as shown in this chart taken from a Novatel tutorial.

If you are using a single frequency receiver then there are usually no good alternatives to using a distant CORS station. The best you can do is collect multiple longer data sets from multiple stations and average the results. If you are using a dual frequency receiver then you also have the option of running a Precise Point Positioning (PPP) solution, either with RTKLIB or through an online service as I describe in this post.

Well, that’s all I can think of at the moment. If anyone else has any additional tips or questions, please add them to the comments below.

30 thoughts on “RTKLIB: Tips for using a CORS station as base”

  1. Hi there, I am trying to use the u-blox zed F9P as a base station for my CORS Network and the C94-M8P as a rover. I have followed this tutorial and tried many solutions bit it didn’t work for me.

    I am using the Emlid Caster (caster.emlid.com) as my NTRIP and configuring it via RTK Lib. Can you please tell me is it possible to communicate these two receivers and establish a cors network?

    My goal is to set up multiple Base stations from the u-blox zed F9P (at least 3) and then test their accuracy.


    1. It is certainly possible to use RTKLIB to connect these two receivers. However, I am not familiar with caster.emlid.com and don’t understand exactly how you plan to combine it with RTKLIB. I would suggest asking this question on the Emlid forum.


  2. Hi,

    I’m logging RTCM MSM7 messages on my rover and using a CORS station as the base. In an attempt to decrease the storage requirement on the rover, I don’t log the navigation/ephemeris messages (1019, 1020). Instead, I’m using the nav files available from the CORS station. However, they provide the GPS and GLONASS nav files as separate files; I wasn’t sure if there was an existing tool in RTKlib that may be used to fuse the nav files into a mixed-type RINEX.

    For now, I have a python script that changes the RINEX header to mixed-type and just appends the GLONASS data at the end of the GPS data (in addition to prepending the satellite IDs with the constellation ID). I suppose this works, but it feels dirty. I wanted to check if there was an existing solution before putting time into making a legitimate tool for this.


  3. Hello everyone,
    Recently I found out that we lost our base statin data for the last 3 months of our project, so we can’t perform PPK kinematic corrections. Luckily for us there is a local CORS provider that is keeping backups of old rinex data and they provided us all we need. The baseline will be in range of 20-65km.
    We are using EMLID REACH M2 for the rover, so u-blox ZED-F9P.
    The CORS provider is using TRIMBLE BD970 for the base.
    Our workflow and automations are based around demo5_b34g, so that is what what we are using to process the data.

    The problem is that even when we have a fix solution the result is with significant error. I expected decimeters range error, but in most of the cases it reaches more than 1 meter. We experimented a lot with the settings, but we can’t get rid of this. Did any of you experienced such an problem? Are we out of lock and we can’t rescue the project? Any suggestion how to remediate it are more then welcome.

    I’ve created a small 10 min dataset to experiment with. The rover stays in a fixed position with a well known location for the duration of the test. If a solution is created with the closest base station(1.2km), everything matches perfectly, but we have 0.75m horizontal error in fix when a base station with 67km baseline is used. If you are quercous, you can check the dataset here:


    1. Hi,

      I’m a newbie at this so certainly take what I say with a grain of salt. I looked at your test dataset and ran it through post-processing and got better results than what you were seeing. The 1.2km solution was certainly better, but comparing that to the 67km solution they only disagreed by around 50mm at worst. The data from rover and base looked pretty ideal from my experience. Here’s a link to some screenshots I took comparing the solutions and also included my configuration file so you can try it with my exact settings. I didn’t do anything special for the settings, it was just what I had loaded up at the time.

      This is just comparing the output between the 2 base stations. I may have misunderstood which error you were talking about.


        1. The test data set is collected in a single location just to make it easier to analyze, but actually we need kinematic solution.
          Strangely when I run your config in kinematic, it gives a 100% float solution, but with much better result then my partially fix solution that gives huge error. I’ll upload my config based on f9p_ppk.conf in the folder from my initial message.


          1. I was looking at the data again and was able to improve the solution. I started fresh with f9p_ppk.conf and made several changes which just make it work better with a 1hz sample rate as opposed to what I think the config was designed for (5hz):
            set pos2-arlockcnt=5, set pos2-arminfix=10, and set the filter type to “combined – no phase reset”.

            After making these changes (and making sure i was in kinematic mode!), I’m getting 99.6% fix solution with maximum deviation looking to be about 20cm. I added these solutions and the updated config file to the folder shared previously.


          2. Edit: I see now it looks like you were recording at 5hz, so I would forego those changes. Using f9p_ppk.conf and changing the solution to “combined – no phase reset” gives similar result.


          3. Actually the rover is 10Hz and the base data is 1Hz. Your conf and suggestions push further my experiments so now I have a conf(new_conf_v2.conf) file that works well with the test dataset, but also with the real data. It turned out that the test dataset is not a very good representative of all the challenges of the real data, because the rover is stationary and this helps to find a fix even with a log baseline.

            pos2-arlockcnt=300 (30sec) one one of the things that improved the situation. Also reintroducing all the constellations, because on previous days I started to exclude some of them in an effort to improve the results.

            One of the things that I experimented blindly is setting stats-errphasebl=0.01, and it helps a lot with having a proper fix with baseline up to 60km. I can’t find any information what this perimeter dose, so I’m plan to check out the source code before going further. I’ll be more then happy if someone have an idea what it does and what is the best value?


          4. The errphasebl parameter adjusts the observation weighting based on the baseline distance between rover and base. Units are meters per 10 km and indicates the adjustment to the standard deviation of the carrier phase observations. For short baseline solutions, zero is usually the best value but in your case, since you are using long baselines, a non-zero value would make sense.


  4. Are there tutorials out there for how to get STRSVR to deal with public CORS information. I’ve been unsuccessful in getting maCORS as a public base station and then making the corrections…


    1. Hi Bostonmacosx. STRSVR is a stream server app that can be used to input a data stream from one source and send it to another but will not process the data itself. I’m assuming you are trying to use it to stream NTRIP data from a real-time CORS station to a receiver for an RTK solution. Documentation for using STRSVR is in the RTKLIB manual. I also have some tips on doing this in this post


      1. Hi there….so I think now I know I want to use RTKNAVI to realtime correct the antenna data…so 1 rover and the public CORS…what is happening though is that the public CORS is timing out repeatedly although my username port address and mount_point are all correct…..hmmm


  5. Hi, thanks a lot for your work.
    As a drone mapping compagny, we work with the EMLID RS2.
    Do you know if this workflow can apply to collect GCPs if you stand within a 50km radius from a CORS station ? (free rinex files from state operated stations)


    1. My experience with F9P (L1/L2, no L5 collection) is that fix is much better if Galileo is available at CORS station. If only GPS+Glonass are available, 50 km could be challenging (although GCPs for drone are supposed to be in open field areas so it could be easier). With GPS + Galileo, it should not be a problem.


    2. Hi MF Drone. Accuracy will decrease as you get farther from the CORS station, so ideally you would be closer than 50 km, but with a dual frequency receiver, you should usually be able to get a fixed solution at this distance. You might also consider using free online PPP solutions from CSRS or other similar services.


  6. Side question. This week-end, I was in an area that was surrounded by a half-dozen of reference stations… between 50 and 60 km away. Is there any method to calculate his own Virtual Reference Station using data from surrounding stations?


    1. Hi Eric. I’m not aware of any open source or reasonably priced software to generate VRS observations. One option is to run solutions against all the nearby bases and combine the results for higher fix rates and better accuracy.


  7. Hello!
    Thank you again for the great work!
    I used the last demo (demo5_b33c) to process some data from the phones Mi8 and Pixel4.
    I noticed that when I look at the multipath indicators (MP) for E1 versus elevation it does not show me anything but it shows to me the MP values versus elevation for E5.
    Do you have any hint on the source of the issue?
    Many thanks again

    Liked by 1 person

  8. I’m using a f9p receiver as a rover with a local CORS station (within 1km) as the base, and using Lefebure NTRIP client for android for an RTK connection, however the altitude reported out of it when I use Mapit GIS seems to only change in 0.1m increments and the number is always .x449. Is anyone using this same setup and experienced this? I’ve tried using the mapit GIS NTRIP client which uses (some) version of rtklib, but could never get it to work.


    1. Found the solution to this, by default the NMEA messages output from the F9P only have 1dp for the altitude and 5dp for lat/long. Enabling CFG-NMEA-HIGHPREC in the config gives 7dp for lat/long and 3dp for alt, so fixes this problem.


  9. I am curious how best to get the difference to set in GLO HW BIAS when dealing with CORS hardware which is predictably different.

    RTKLIB seems to ignore the offset in RTCM MT1230. I think it also ignores the GLONASS COD/PHS/BIS record in the RINEX3 file from CORS.

    I am not sure why that is, but surely it would make sense to use the actual biases from the base rather than the prediction from your extended “Wanninger” table so I am wondering whether I can enter these manually but I am a bit lost.

    eg you have Trimble=-0.7cm but CORS with a TRIMBLE ALLOY has +19.06m in RTCM/RINEX
    eg you have Leica=+2.3cm but a CORS with a LEICA GR30 has -71.94m in RTCM/RINEX

    Are these dealing with the same concept in a different manner? Is there a formula to convert between these two types?


    1. Hi Arl. Yes, RTKLIB ignores the COD/PHS/BIAS records in the RINEX files, and does not decode the RTCM MT1230 messages. I agree that these would be nice additions to RTKLIB and they are on my list of possible future code improvements. None of the CORS stations near me include COD/PHS/BIS records in the rinex files so I do not know if they are equivalent to the values used by RTKLIB and the Wanninger table. I describe how to find/verify the correct bias values for RTKLIB in this post, or if you’d like to send me rover and base data for an example with these fields, I’m curious to see if this is true. Note that if you do this experiment yourself and just want to verify a proposed bias value, you do not need to modify the initial variance or process noise (pos2-arthres3 and pos2-arthres4) for the hardware bias states. You can just set Glonass AR to “autocal”, the Glonass HW bias (pos2-arthres2) to the proposed bias value, run the solution, and then plot the residuals with RTKPLOT as described in the above post. If the proposed bias is correct, the Glonass carrier phase residuals should converge to near zero after the solution achieves fix.


      1. Hi Arl. One more thought on automating the Glonass hardware bias input to RTKLIB. Even with the existing code, it would be fairly easy to write a short python script to extract either the receiver type or bias from the rinex file and use the result to update the bias parameter in the config file before running the RTKLIB solution.


        1. Indeed – but first we need to work out how to convert one measurement to the other!

          I shall have to do some experimentation I think (or send you some data)

          Did you see the Gsilib fork which purported to save and load Glonass biases from a .tbl file (indexed by the Receiver type string I think)?

          I notice that the CORS MSM messages show L1P and L2P data, but Ublox is L1C/A and L2C/A, will that be an issue?


          1. Hi Ari. I haven’t seen the Gsilib fork but I will take a look at it. It’s OK to combine the L1P/L2P and the L1C/L2C observations in an RTK solution, although if one receiver provides a mix of the two, it can confuse RTKLIB.


    1. Hi Sbd. If you are using rinex files generated from VRS data streams for base station data, then just about everything in the post should apply. I find that most of the time VRS data works well, but sometimes there seems to be large errors in the data, and I get a much better solution from the nearest CORS station. This is with single frequency receivers though, I don’t know if this applies to dual frequency solutions as well.


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: