So I’ve been chasing something for a couple of days and I figured I’d just post it, partly in case it helps someone and partly because I’m not totally sure I’m reading it right.
I’ve got a u-blox on a small rover doing 1 Hz fixes. The receiver puts ~5m horizontal 1-sigma in position_covariance, which honestly seemed believable for where I was driving. But then I looked at how much the fixes actually move relative to each other, and the median second difference is around ~0.1 m. The worst one in the whole run was about 1 m. If the noise were really white at ~5m I’d have expected that number to be somewhere around 14 m.
So there’s roughly a factor of 160 between what it says and how it behaves.
My guess, and this is the bit I’d like a sanity check on, is that the receiver smooths internally. So the 5m is the absolute error, mostly multipath, and it’s an honest number. But the fix-to-fix noise is centimetres because consecutive fixes are now heavily correlated. Which would mean the covariance is true, it’s just not the thing a Kalman filter is asking for, since the filter assumes the noise is white and independent.
Anyway, if that’s right then it explains my gate. My NIS sits around 0.03 where it should be near 3, so the chi-squared threshold ends up miles above anything that ever actually happens and nothing can trip it.
Two things I genuinely don’t know:
- Is this a u-blox thing specifically, or do other receivers do the same? I’ve only got the one so I can’t really tell.
- And what do people actually do about it in practice? Shrinking R feels wrong to me, because then you’d just track the multipath bias and confidently report centimetre accuracy while sitting metres off. I’ve seen a slowly varying GPS bias state mentioned as the proper fix but I haven’t tried it yet.
Here’s the script I used if anyone wants to check theirs. Works on any bag with a NavSatFix, nothing else needed:
#!/usr/bin/env python3
"""python3 gps_cov_check.py <bag_dir>"""
import sys, math, statistics, rosbag2_py
from rclpy.serialization import deserialize_message
from rosidl_runtime_py.utilities import get_message
bag = sys.argv[1]
r = rosbag2_py.SequentialReader()
r.open(rosbag2_py.StorageOptions(uri=bag, storage_id=""), rosbag2_py.ConverterOptions("", ""))
topic = next(t.name for t in r.get_all_topics_and_types()
if t.type == "sensor_msgs/msg/NavSatFix")
r.set_filter(rosbag2_py.StorageFilter(topics=[topic]))
M = get_message("sensor_msgs/msg/NavSatFix")
lat, lon, sig = [], [], []
while r.has_next():
_, data, _ = r.read_next()
m = deserialize_message(data, M)
if m.position_covariance_type == 0:
continue
lat.append(m.latitude); lon.append(m.longitude)
sig.append(math.sqrt(max(m.position_covariance[0], 0.0)))
R = 6371000.0
xs = [math.radians(v - lon[0]) * R * math.cos(math.radians(lat[0])) for v in lon]
ys = [math.radians(v - lat[0]) * R for v in lat]
d2 = sorted(math.hypot(xs[i+1] - 2*xs[i] + xs[i-1], ys[i+1] - 2*ys[i] + ys[i-1])
for i in range(1, len(xs) - 1))
declared = statistics.median(sig)
observed = d2[len(d2) // 2]
expected = math.sqrt(6.0) * 1.1774 * declared
print("declared 1-sigma %.2f m" % declared)
print("median |2nd difference| %.3f m" % observed)
print(" if that were white %.1f m" % expected)
print("ratio %.0fx smoother" % (expected / max(observed, 1e-9)))
Would be curious what ratio other people get and which receiver you’re on, especially if anyone’s running RTK fixed or something that doesn’t smooth its output. Thanks