Broadcast-Only Kinematic PPP in Urban Driving¶
What limits gnss_ppp --kinematic with broadcast ephemerides only (no precise
or SSR products) on the PPC 2024 urban drives, what was fixed, and what is left.
Result¶
PPC 2024 Tokyo run1 and Nagoya run1 (rover.obs + base.nav, 5 Hz, float
PPP, default white-noise position re-seeded from SPP every epoch), scored per
epoch against reference.csv. H is the horizontal and U the absolute up error;
availability is the share of reference epochs with an output solution.
| Solver | Tokyo H50 / H95 | Tokyo U50 / U95 | Tokyo avail. | Nagoya H50 / H95 | Nagoya U50 / U95 | Nagoya avail. |
|---|---|---|---|---|---|---|
gnss_ppp --kinematic, develop after PR #537 |
7.58 / 42.43 m | 19.68 / 56.01 m | 99.29 % | 5.36 / 20.26 m | 4.69 / 57.03 m | 98.80 % |
gnss_ppp --kinematic, this change |
0.80 / 4.88 m | 2.06 / 13.47 m | 99.12 % | 3.27 / 11.60 m | 2.37 / 22.26 m | 98.68 % |
gnss_ppp --kinematic, after the solid-earth-tide frame fix (2026-09-30) |
0.79 / 4.87 m | 2.07 / 13.48 m | 99.12 % | 3.56 / 13.31 m | 2.21 / 32.24 m | 98.68 % |
gnss_ppp --kinematic, BeiDou receiver clocks (2026-10-01) |
0.91 / 5.29 m | 3.26 / 15.26 m | 99.12 % | 1.99 / 10.31 m | 3.18 / 31.28 m | 98.68 % |
gnss_spp (same data) |
1.78 / 20.44 m | 2.09 / 47.94 m | 99.12 % | 2.80 / 11.05 m | 4.11 / 20.85 m | 98.65 % |
| RTKLIB demo5 b34k PPP-kinematic, broadcast (config below) | 3.62 / 18.16 m | 4.32 / 48.96 m | 68.91 % | 5.59 / 11.77 m | 27.82 / 72.67 m | 11.99 % |
same, innovation gates opened (pos2-rejionno=30, pos2-rejcode=100) |
3.90 / 31.68 m | 6.57 / 103.89 m | 96.75 % | 4.57 / 17.76 m | 24.05 / 66.73 m | 96.68 % |
The solid-earth-tide frame fix (the IERS 2010 tide was computed with the
Sun and Moon in the ICRS instead of the Earth-fixed frame; see
Galileo HAS) changes the modelled tide by up to a few
decimetres. Tokyo run1 is unchanged; Nagoya run1 moves to H95 13.3 m and U95
32.2 m, the same as with the legacy Step-1 tide (--no-iers-solid-tide:
3.56 / 13.31 m, U95 32.23 m) or with no solid tide (3.55 / 13.29 m, U95
32.06 m), so its previous U95 of 22 m was a side effect of the mis-framed
tide in a run whose up tail follows metre-level code errors. On runs 2 and 3
H50 moves by -0.06 to +0.13 m and the up statistics move both ways (Nagoya
run2 U50 5.39 -> 6.02 m and U95 31.0 -> 36.0 m, run3 U50 7.44 -> 5.69 m).
The BeiDou receiver clocks (one for BDS-3, one for BDS-2, with the Galileo / QZSS / BeiDou inter-system biases kept across the per-epoch SPP re-seeding of the GPS clock; see the BeiDou note) remove most of the Nagoya east bias (median east error +2.79 -> +1.41 m on run1). The BeiDou code was the cause: the estimated biases are steady at about +7.7 m (BDS-3) and +3.6 m (BDS-2) on all PPC drives, and with the shared GPS clock that code offset leaned the solution east. H50 on the other runs: Tokyo run2 1.33 -> 0.81 m, run3 0.79 -> 0.74 m, Nagoya run2 3.67 -> 2.98 m, run3 3.89 -> 1.85 m (H95 run2 24.8 -> 15.1 m, run3 28.3 -> 18.9 m). The up error grows on most runs (Tokyo run1 U50 2.07 -> 3.26 m, Nagoya run1 2.21 -> 3.18 m, run3 5.69 -> 9.20 m; Tokyo run2 improves 1.91 -> 0.74 m): the biased BeiDou code had been offsetting an up error of the other constellations. Without BeiDou (GPS + Galileo + QZSS) Tokyo run1 has a median up error of +4.5 m; with BeiDou and its clocks, GPS + Galileo + QZSS + BeiDou gives Tokyo run1 H50 / H95 0.95 / 4.27 m, U50 3.12 m and Nagoya run3 H50 1.64 m, U50 2.20 m (U95 14.8 m); the Nagoya run3 up tail of the all-system run comes from GLONASS (GPS + Galileo + QZSS + GLONASS: U50 6.63 m).
Before PR #537 (one measurement update per epoch) Tokyo run1 was at
18.1 / 117 m horizontal. The 29 epochs (20 Tokyo, 9 Nagoya) no longer output are epochs where the SPP
seed failed and the old filter coasted on a stale position with 4-6
satellites; their errors were 9 m to 5 km. For --use-dynamics-model see
Dynamics model.
The Galileo HAS kinematic lane (gnss reproduce has-idd-ppp, OBE4 static
antenna processed kinematically) also benefits from the residual screening:
over the hour after the first 10 minutes the horizontal RMS drops from
0.156 m to 0.086 m and the up RMS from 0.206 m to 0.099 m; see
Galileo HAS support.
On open sky the change is neutral: broadcast-only kinematic PPP on the OBE4 hour stays at H / U RMS 0.136 / 0.230 m after the first 10 minutes (0.139 / 0.220 m before). CLAS and MADOCA output is byte-identical (CLAS never reaches this filter, coherent MADOCA is excluded from the screening and carries SSR code biases), as are the HAS / legacy SSR static runs. The BeiDou and single-frequency fixes also apply to broadcast-only static runs that contain BeiDou or single-frequency satellites (the OBE4 hour has neither, so its broadcast static output is unchanged).
Root causes¶
The PPP solution was 2-4 times worse than the SPP seed it starts from every epoch, so the extra error came from the filter, not from the data.
- BeiDou group delays missing (bug). Broadcast (D1/D2) BeiDou clocks are referenced to B3I; B1I and B2I codes carry TGD1 / TGD2, which reach -45 ns (-13.5 m) on BDS-3 satellites. The PPP ionosphere-free code ignored them and paired B1I with B2a (whose group delay is only broadcast in B-CNAV), so BDS-3 rows were biased by 15-45 m (C33 prefit residual -50 m, C42 -36 m, against about -10 m for GPS in the first two minutes of Tokyo run1). The SPP applies TGD1 on its single-frequency B1I rows, which is why the SPP was better. Fix: without precise, SSR or DCB products, BeiDou uses B1I with B2I / B3I and removes TGD1 / TGD2. Tokyo H50 7.58 -> 4.01 m, Nagoya 5.36 -> 3.84 m. (BDS-3 transmits no B2I; its band-7 code is B2b, so BDS-3 pairs B1I with B3I only since 2026-10-01. The PPC rover logs no B2b, so the PPC numbers are not affected by that part.)
- No post-fit residual screening in kinematic PPP (missing safeguard).
The only code gate rejected residuals above 20 km with broadcast
ephemerides. On Tokyo run1 6.9 % of the code rows had prefit residuals
above 30 m and a quarter of the epochs carried at least one row above
100 m (NLOS). They were committed, and the persistent zenith troposphere
state absorbed their positive, low-elevation-heavy excess delay: it ran
from 2.3 m to 20-24 m (8-11 m after the BeiDou fix), which pulled the up
error to tens of metres and fed the float ambiguities, from which the
position recovered only over minutes. RTKLIB / MADOCALIB
ppp_res()excludes the satellite with the largest post-fit residual beyond four sigmas and redoes the epoch from the predicted state. The kinematic filter now does the same, with the residuals standardized by their own covariance (Baarda w-test,w_i = (S^-1 r)_i / sqrt((S^-1)_ii)), until no row exceeds 4 or fewer thanmin_satellitessatellites would remain (then no update is committed and the SPP seed is output). Normalizing by the measurement sigma alone, as RTKLIB does, singles out the phase rows of healthy satellites here (the native phase sigma is about 1 cm and has no broadcast signal-in-space term): Nagoya U95 39 m instead of 22 m. The troposphere now stays between 2.2 and 2.9 m on Tokyo run1. Screening runs for kinematic motion (not--low-dynamics). Coherent MADOCA keeps it off except on the per-frequency AR path, which since 2026-10-18 screens like MADOCALIBppp_res()(see the MADOCALIB migration notes). CLAS never reaches it. - Single-frequency rows without ionosphere correction (bug). When a satellite's second frequency is missing (32 % of the GLONASS and about 3 % of the GPS / BeiDou satellite-epochs on Tokyo run1), the ionosphere-free filter used its raw L1 code, with the full ionospheric delay (10 m at L1 on this solar-maximum afternoon), and tied its raw L1 phase to the satellite's ionosphere-free ambiguity. With broadcast ephemerides such rows now remove the broadcast group delay (as the SPP does) and the Klobuchar ionosphere with RTKLIB's 50 % error variance, and use code only. Tokyo H95 11.8 -> 4.9 m.
Other suspects checked and ruled out on this data:
- Cycle slips / outages. A RTKLIB-style ambiguity reset after an outage (2 or 10 s) changed Tokyo / Nagoya H50 by less than 0.2 m; loss-of-lock flags and the geometry-free / Melbourne-Wubbena tests already reset the ambiguities after blockages.
- Broadcast signal-in-space variance. Adding the broadcast URA to the code variance was neutral to negative (Tokyo H50 0.81 -> 1.36 m, Nagoya 3.25 -> 2.98 m); adding it to the phase variance as RTKLIB does made both runs worse (Tokyo H50 2.52 m, Nagoya U50 7.9 m).
- Estimating Galileo / BeiDou / QZSS inter-system biases
(
GNSS_PPP_ESTIMATE_ISB): mixed (BeiDou ISB: Nagoya H50 3.22 -> 1.89 m but H95 12.3 -> 15.3 m, Tokyo H50 0.77 -> 1.56 m), left off. Those runs re-initialized the system clocks every epoch, so the bias was re-estimated from scratch; since 2026-10-01 BeiDou has its own clocks by default with broadcast ephemerides and the biases are kept across epochs (see above).
Remaining limitations¶
- Metre-level floor at Nagoya. Before the BeiDou receiver clocks Nagoya run1 had H50 2.5-3.6 m (Tokyo 0.6-0.9 m), a slowly varying east bias of 1-3 m that tracked BeiDou and GLONASS (GPS + Galileo + QZSS only: H50 1.78 m; with BeiDou 2.89 m, with GLONASS 2.09 m, with both 3.27 m). The BeiDou part was the missing BeiDou receiver clock (now H50 1.99 m, median east +1.41 m; GPS + Galileo + QZSS + BeiDou 1.70 m). GLONASS remains (one GLONASS clock re-initialized every epoch, no inter-frequency code biases), as does the SPP, which still shares one clock across GPS / Galileo / QZSS / BeiDou (all systems H50 2.80 m, GPS + Galileo + QZSS 0.82 m).
- Up error. The kinematic up error is metre-level on every run (median +3 m on Tokyo run1 and Nagoya run1, +9 m on Nagoya run3 with GLONASS) and is not explained by BeiDou: GPS + Galileo + QZSS alone has a median of +4.5 m on Tokyo run1.
- Urban canyons with few satellites. Below 15 satellites the Tokyo error is 2-8 m median, above 20 satellites 0.6-0.9 m (H95 about 2 m): the remaining tail is geometry and NLOS the screening cannot identify.
--use-dynamics-modelrelies on the SPP seed check. The guard that re-seeds the motion states when the predicted position disagrees with SPP (below) fires on 4.4 % of the Tokyo run1 epochs. Where the SPP itself is tens of metres off for a long stretch, the dynamics output follows the re-seeded SPP on those epochs; Tokyo run3 H95 (5.99 m against 4.03 m with the default white-noise position) and Nagoya run1 H95 (11.4 m against 10.3 m) are the two tails it does not improve.
Dynamics model (--use-dynamics-model, 2026-10-01)¶
Outside CLAS, --use-dynamics-model diverged with broadcast ephemerides
(hundreds of metres to kilometres before PR #541; since the screening it
rejected the inconsistent updates and printed the SPP seed, Tokyo run1 H50
1.78 m). It now follows RTKLIB's ppp-kine with pos1-dynamics=on
(udpos_ppp() / udclk_ppp(), items 1 and 2) and adds two safeguards
(items 3 and 4):
- Receiver clock re-seeded from SPP every epoch. The mode switched the
clock re-seed off and the broadcast / SSR clock had no process noise
(
process_noise_clock = 0), so the filter held the receiver clock while the PPC rover clock drifts by about 150 m/s. RTKLIB re-initializes the clock every epoch (udclk_ppp(),VAR_CLK = 60^2); the dynamics mode now does the same, keeping the Galileo / QZSS / BeiDou inter-system biases as the default mode does. - Position / velocity / acceleration states. The filter had position and
velocity only, with random walks of 0.04 m^2/s on the position and
0.01 m^2/s^3 on the velocity. It now carries three acceleration states
(
x = [r v a],r += v dt + a dt^2/2,v += a dt) with process noise on the acceleration only, 1 m/s^2/sqrt(s) horizontal and 0.1 m/s^2/sqrt(s) vertical in the local frame (--process-noise-accel-h / -v; RTKLIBstats-prnaccelh / v). Velocity is initialized from the SPP Doppler velocity when the SPP has one. As in RTKLIB the motion states are re-seeded from SPP when the mean position variance exceeds the initial 60^2 m^2, tested on the predicted covariance so that an outage of several seconds restarts from SPP, and no PPP update is made on an epoch without an SPP seed (RTKLIB skips PPP whenpntpos()fails). Without that, 15 s without a seed on Tokyo run1 ended in a 2.4 km update from five satellites. - Doppler velocity update. Every epoch the velocity states are updated with the SPP Doppler velocity and its covariance before the GNSS measurement update. The velocity is otherwise observed only through the carrier-phase rows; after a few rejected or code-only epochs a velocity error of a few m/s was integrated into the position (Nagoya run1: a 4 m/s drift that ended 55 m east). RTKLIB does not use Doppler in PPP.
- SPP disagreement guard. The motion states are re-seeded from SPP when
the predicted position and the SPP seed disagree beyond the 99.9 % bound
of a chi-square with three degrees of freedom,
d' (P + C_spp)^-1 d > 16.27. With a small position covariance the post-fit screening otherwise rejects the code rows that disagree with a wrong prediction, so the filter cannot recover (Tokyo run1 under NLOS at tow 189460: the position oscillated by up to 300 m with 11-27 of about 30 satellites rejected per epoch).
Scope: kinematic gnss_ppp with broadcast, SP3 / CLK or SSR (HAS, RTCM)
products. --clas-osr keeps its MRTKLIB dynamics and clock handling
unchanged (CLAS tokyo_run1 output byte-identical), MADOCA L6 runs keep the
previous settings until they are compared with the MADOCALIB bridge, and
--low-dynamics still disables the dynamics. Runs without
--use-dynamics-model are byte-identical.
PPC 2024 run1 (scored as above):
| Solver | Tokyo H50 / H95 | Tokyo U50 / U95 | Tokyo avail. | Nagoya H50 / H95 | Nagoya U50 / U95 | Nagoya avail. |
|---|---|---|---|---|---|---|
gnss_ppp --kinematic (default white-noise position) |
0.91 / 5.29 m | 3.26 / 15.26 m | 99.12 % | 1.99 / 10.31 m | 3.18 / 31.28 m | 98.68 % |
--use-dynamics-model, develop (outputs the SPP seed) |
1.78 / 20.61 m | 2.10 / 48.63 m | 99.12 % | 2.81 / 11.57 m | 4.13 / 22.02 m | 98.65 % |
--use-dynamics-model, items 1-2 only |
0.89 / 11.11 m | 3.67 / 22.36 m | 99.12 % | 1.59 / 30.65 m | 2.36 / 79.64 m | 98.65 % |
| + Doppler velocity update | 0.85 / 5.40 m | 3.50 / 13.95 m | 99.12 % | 1.43 / 41.03 m | 2.90 / 16.31 m | 98.65 % |
| + SPP disagreement guard (this change) | 0.74 / 4.01 m | 3.35 / 9.48 m | 99.12 % | 1.71 / 11.41 m | 2.86 / 26.72 m | 98.65 % |
RTKLIB demo5 b34k ppp-kine, pos1-dynamics=on |
2.77 / 19.34 m | 2.00 / 36.01 m | 34.01 % | 4.67 / 11.04 m | 16.59 / 30.98 m | 14.73 % |
same, gates opened (rejionno=30, rejcode=100) |
3.47 / 19.60 m | 3.81 / 69.86 m | 75.29 % | 3.37 / 15.34 m | 17.49 / 57.81 m | 74.92 % |
same, gates opened, prnaccelh=1, prnaccelv=0.1 |
3.23 / 17.82 m | 4.29 / 69.05 m | 76.25 % | 3.51 / 16.01 m | 18.20 / 56.18 m | 76.70 % |
All dynamics rows use the default acceleration noise (1 / 0.1). The RTKLIB
rows use the configuration in Reproduce with
pos1-dynamics=on (demo5 default acceleration noise 0.1 / 0.01
m/s^2/sqrt(s)).
All six drives, default white-noise position against the dynamics model:
| Run | default H50 / H95 | dynamics H50 / H95 | default U50 / U95 | dynamics U50 / U95 |
|---|---|---|---|---|
| Tokyo run1 | 0.91 / 5.29 m | 0.74 / 4.01 m | 3.26 / 15.26 m | 3.35 / 9.48 m |
| Tokyo run2 | 0.81 / 4.48 m | 0.63 / 3.25 m | 0.74 / 10.30 m | 0.83 / 4.19 m |
| Tokyo run3 | 0.74 / 4.03 m | 0.64 / 5.99 m | 3.08 / 6.90 m | 2.97 / 6.38 m |
| Nagoya run1 | 1.99 / 10.31 m | 1.71 / 11.41 m | 3.18 / 31.28 m | 2.86 / 26.72 m |
| Nagoya run2 | 2.98 / 15.05 m | 2.60 / 9.19 m | 6.81 / 38.23 m | 6.73 / 27.90 m |
| Nagoya run3 | 1.85 / 18.86 m | 1.85 / 6.65 m | 9.20 / 56.45 m | 8.59 / 24.36 m |
Availability is unchanged except Nagoya run1 and run2 (-0.03 %, epochs without an SPP seed). Acceleration noise 3 / 1 m/s^2/sqrt(s) gives a similar picture (mean H50 1.26 m against 1.36 m over the six runs, mean H95 6.73 m against 6.75 m, mean U95 15.9 m against 16.5 m); 1 / 0.1 is the default because it is also neutral on a static antenna.
OBE4 hour processed kinematically (RMS after the first 10 minutes, H / U): HAS IDD 0.085 / 0.102 m by default, 0.196 / 0.442 m with the develop dynamics mode and 0.084 / 0.100 m now; broadcast only 0.138 / 0.237 m by default, 0.238 / 1.168 m on develop and 0.138 / 0.236 m now; IGS final SP3 / CLK 0.265 / 0.317 m in all three (that path already had a clock process noise).
Reproduce¶
gnss_ppp --obs PPC-Dataset/tokyo/run1/rover.obs --nav PPC-Dataset/tokyo/run1/base.nav \
--kinematic --out tokyo_run1_ppp.pos
# dynamics model (add --process-noise-accel-h 3 --process-noise-accel-v 1 for
# the 3 / 1 comparison)
gnss_ppp --obs PPC-Dataset/tokyo/run1/rover.obs --nav PPC-Dataset/tokyo/run1/base.nav \
--kinematic --use-dynamics-model --out tokyo_run1_ppp_dyn.pos
RTKLIB demo5 b34k rnx2rtkp -k conf rover.obs base.nav with:
pos1-posmode =ppp-kine
pos1-frequency =l1+l2
pos1-soltype =forward
pos1-elmask =15
pos1-dynamics =off
pos1-tidecorr =off
pos1-ionoopt =dual-freq
pos1-tropopt =est-ztd
pos1-sateph =brdc
pos1-navsys =61
pos2-armode =off
out-solformat =xyz
(demo5 defaults otherwise, including pos2-rejionno=5 / pos2-rejcode=30;
the second RTKLIB row adds pos2-rejionno=30 and pos2-rejcode=100. On the
Windows build the input paths must use backslashes.)