GPS is a really nice and quite cheap technology – unless you want accuracy in orders of decimeters or centimeters. Yes, there are high-accuracy GPS receivers which can achieve such an accuracy, however the price of the cheapest one is starting at $500. Not really a something a hobbyist can afford/is willing to pay for.
For many application there’s no need for absolute position, a relative position of 2 or more GPS receivers is sufficient. Take an autonomous robot moving in outside area as an example. You can setup a fixed ground station and navigate it relative to it. The intuition tells you that measuring relative position should be more accurate (when we’re talking about station less than 500 m away from each other) that measuring the absolute position int the WGS-84 system.
Why? The most of the GPS receiver’s accuracy depends on the atmospheric delay (the time the GPS signal travels through air) and it should be pretty much-less the same in a small ground area. So, having two the same GPS receivers, which uses the same antennas and algorithms for position measurement, should produce the same results, right? Well, kinda.
There are many papers out there about relative GPS position measurements. I’ve read several of them and none of them and I have to say they were bad. All of them were very shallow with lots of motivation and no results (don’t even think about public implementation!). I gave up reading (for now).
The Naive Approach
I wanted to test the basic, simple idea – the same receivers in the same conditions should produce the same results. So I got 2 u-blox NEO-6M GPS receiver modules with a patch antenna, which you can nowadays get for less than $4 on AliExpress (unbelievable).
Getting these GPS modules running is not a big deal. They use the NMEA standard to communicate through serial line. After few minutes of googling I found out the modules are 5V tolerant. So I connected them to PC using FTDI USB to serial converter. I used the factory settings. U-blox provides u-center application for Windows, which can configure them through GUI. There are plenty of settings, however I can say no settings appears to change accuracy in a significant way (at least what I can tell after an afternoon spent on playing with various configurations).
Then I wrote a simple Python script, you can find at the end. This script reads and parses NMEA output of two modules every second and computes relative XY position of the obtained coordinates using a local projection. It produces a file of values, which can be easily plotted using gnuplot.
I oriented the antennas in the same way and attached them on a ruler so there is a 10 cm gap between them. I took this module outside, place it on ground and let the GPS receivers to “view the clear sky” for 3 minutes. I used 2 modes – the first considers every position as independent of the other. The second mode computes average of the last 20 positions.
Results
The results are really, really bad as you can see in the chart. Even the GPS modules measure in the same time intervals (they were powered-up simultaneously) and use the same antennas, the results are as noisy as an absolute position measurement. The error of the relative positions is up to 8 meters. So, the original hypothesis about same modules and conditions was not confirmed. This is not the way to go. If I want better accuracy, I should read more literature and probably look at the RTKLIB.
Python Script
#!/usr/bin/env python3 import sys import time import collections from math import sqrt import pynmea2 from pyproj import Proj, transform from serial import Serial class GPS: """Interface for NMEA GPS device connected through serial line""" def __init__(self, port, baudrate=9600): self.port = Serial(port=port, baudrate=baudrate, timeout=0) self.buffer = "" samples = 20 self.lon = collections.deque(maxlen=samples) self.lat = collections.deque(maxlen=samples) self.sat = None def update(self): """Reads data from serial line and parses new sentence""" while True: read = self.port.read() if read is b'': return if read == b'\x00': continue self.buffer += read.decode('utf-8') if self.buffer[-1] == '\n': try: msg = pynmea2.parse(self.buffer[:-1]) if hasattr(msg, "num_sats"): self.sat = msg.num_sats if hasattr(msg, "lon"): self.lon.append(msg.longitude) if hasattr(msg, "lat"): self.lat.append(msg.latitude) except pynmea2.nmea.ParseError: print("Parsing error") self.buffer = "" return True return False def fix(self): """True if at least one position was obtained""" return len(self.lon) > 0 and len(self.lat) > 0 def get_lat(self): return sum(self.lat) / len(self.lat) def get_lon(self): return sum(self.lon) / len(self.lon) def print_gps(outfile, gps1, gps2): """Prints GPS status""" # outfile.write("{0:.20f}\t{1:.20f}\t{2:.20f}\t{3:.20f}\n" # .format(gps1.get_lon(), gps1.get_lat(), # gps2.get_lon(), gps2.get_lat())) # outfile.flush() wgs84 = Proj('+proj=longlat +datum=WGS84 +no_defs') # Assign custom location for XX and YY nad27Blm15n = Proj('+proj=tmerc +lat_0=XX +lon_0=YY +k=0.9996 +x_0=500000.001016002 +y_0=0 +datum=NAD27 +no_defs') # http://epsg.io/32065 but in meters point1 = transform(wgs84, nad27Blm15n, gps1.get_lon(), gps1.get_lat()) point2 = transform(wgs84, nad27Blm15n, gps2.get_lon(), gps2.get_lat()) diff_x = point1[0] - point2[0] diff_y = point1[1] - point2[1] dist = sqrt(diff_x * diff_x + diff_y * diff_y) outfile.write("{0:.20f}\t{1:.20f}\t{2:.20f}\n".format(diff_x, diff_y, dist)) outfile.flush() sys.stdout.write("{0:.20f}\t{1:.20f}\t{2:.20f}\t{3:.20f}\n" .format(gps1.get_lon(), gps1.get_lat(), gps2.get_lon(), gps2.get_lat())) sys.stdout.write("{0:.20f}\t{1:.20f}\t{2:.20f}\n".format(diff_x, diff_y, dist)) def main(): """Entry point""" if len(sys.argv) != 4: print("Wrong usage!") print("Please use: gps.py tty1 tty2 file") sys.exit(1) gps1 = GPS(sys.argv[1]) gps2 = GPS(sys.argv[2]) lines = 0 with open(sys.argv[3], "w") as file: start_time = time.time() while True: gps1.update() gps2.update() tim = time.time() if tim - start_time > 1: print_gps(file, gps1, gps2) start_time = tim lines += 1 print("Written {0} lines".format(lines)) time.sleep(0.1) if __name__ == "__main__": main()
Recent news: My open letter to the 3D-printing community
I love the 3D-printing community, but I think there is room for improvement. Let's get better in 2023! Read the full letter.
Support my work!
If you like my work (these blog posts, my software and CAD models) and you would like to see more posts on various topics coming, consider supporting me in various ways:
- You can become my sponsor on Github.
- If you prefer, you can also become my Patreon.
- You can buy me a coffee on Ko-fi,
- or you can buy something from my Tindie store (also see below),
- Or you can just share my work!
If you are interested in knowing what I am up to and recent sneak-peaks, consider following me on social media (Twitter, Instagram, Facebook).
My store offers
I launched new tank cleaning kits for Elegoo Saturn, Saturn S, Mars 1, and Mars 3. You can find them in my store.>
Hi,
Thanks for sharing your experiments of this great blog 🙂
Did you setup the receivers (if possible with these) so they use the same set of satellites ?
I f you use different sets for both, especially with some satellites close from the horizon, this could somehow explain the differences.
i tried to set the same satelites, but the results were more-less the same. However, I discovered an interesting paper (link below) few days after this experiments. Now I working on a port of their code to these cheap modules. I already discovered how to enable raw output of these modules, now I am working a wireless bridge for the GPS receivers. However I am stacked with work, so this project is taking me too long to complete.
http://www.isis.vanderbilt.edu/projects/relativeGPS
Hi Jan
I know the this blog is old but it is something i am looking at. Did you achieved anything with relative improving accuracy with cheap GPS ?
The project was (unfortunately) never finished. So no progress here.
Hi Jan, I hope you are doing great
We work on a graduation project trying to improve the accuracy of gps signal positioning using (Ublox NEO-7m GPS Module) Did you get any new information about this subject? Can I contact you about this?