Native non-GUI GNSS stack in modern C++17 with built-in SPP, RTK, PPP, CLAS/MADOCA, RTCM, UBX, and direct QZSS L6 handling.
The point of this repo is simple: ship a usable GNSS toolchain without depending on an external RTKLIB runtime.
If this repo is useful, star it.
Contribution and PR workflow: CONTRIBUTING.md Architecture notes: docs/architecture.md Documentation index: docs/index.md
QZSS CLAS (Centimeter-Level Augmentation Service) PPP from raw L6 binary, 2019-08-27 static dataset (TRM59800.80 antenna), 1 hour (3599 epochs):
| Metric | gnssplusplus --claslib-parity |
CLASLIB |
|---|---|---|
| Matched fixed epochs | 3594 / 3599 (99.86%) | 3594 / 3599 (99.86%) |
| RMS 3D (fixed-only) | 3.57 mm | 7.29 mm |
| 3D bias (mean offset) | 1.66 mm | 4.84 mm |
| RMS East | 1.15 mm | 1.52 mm |
| RMS North | 1.21 mm | 0.92 mm |
| RMS Up | 3.15 mm | 7.07 mm |
| Mean E / N / U | -0.72 / +0.93 / +1.17 mm | +0.65 / -0.59 / +4.76 mm |
| First fix epoch | epoch 6 | epoch 6 |
| CLASLIB runtime link | not required | required |
| Parity depth | 17 helpers at 1e-6 m vs CLASLIB oracle | reference |
| CLASLIB 2D | gnssplusplus 2D |
|---|---|
![]() |
![]() |
gnssplusplus achieves 51% lower RMS 3D and ~3x tighter 3D bias than upstream CLASLIB on the same 1-hour window while keeping the same fix rate, with no CLASLIB runtime dependency on the default path. The ClasnatParity GoogleTest suite pins 17 core helpers (windupcorr, antmodel, ionmapf, prectrop, corrmeas, satpos_ssr, tidedisp, eph2clk, eph2pos, geodist, satantoff, compensatedisp, trop_grid_data, filter, lambda, tropmodel, stec_grid_data) to 1e-6 m parity against the CLASLIB C source.
Opt-ins:
-DCLASLIB_PARITY_LINK=ON+--claslib-bridge: delegate to upstream CLASLIBpostpos()linked as a static library (oracle mode)--legacy-strict-parity: iter13-era non-native strict OSR path (regression reference)
See docs/clas_port_architecture.md for the port design and docs/clas_validated_datasets.md for the validated dataset set.
gnssplusplus develop (post PR #19–#23) dominates RTKLIB demo5 on the
PPC-Dataset Tokyo and Nagoya urban runs across Fix count, Fix rate, and
precision — with no Phase 2 opt-in flags. UrbanNav Tokyo Odaiba is dominated
on Fix count, Hp95, and Vp95; Hmed sits within 9 cm of demo5 once
--enable-wide-lane-ar --wide-lane-threshold 0.10 is opted in.
All runs below use --mode kinematic --preset low-cost --match-tolerance-s 0.25.
| Run | gnssplusplus Fix / rate | RTKLIB Fix / rate | Hmed (m) | Vp95 (m) |
|---|---|---|---|---|
| run1 | 3572 / 81.26% | 2418 / 30.52% | 0.037 vs 1.567 (42×) | 1.259 vs 36.703 (29×) |
| run2 | 4674 / 80.12% | 2127 / 27.58% | 0.016 vs 0.835 (52×) | 0.313 vs 42.624 (136×) |
| run3 | 7516 / 86.84% | 5778 / 40.55% | 0.012 vs 0.666 (56×) | 0.137 vs 24.521 (179×) |
| Run | Fix delta | rate delta | Hmed delta |
|---|---|---|---|
| run1 | +1743 | +58.03 pp | 9× better |
| run2 | +1735 | +64.00 pp | 10× better |
| run3 | +154 | +50.16 pp | 44× better |
| Config | Fix | Rate | Hmed (m) | Hp95 (m) | Vp95 (m) |
|---|---|---|---|---|---|
| RTKLIB demo5 | 595 | 7.22% | 0.707 | 27.878 | 45.212 |
| gnssplusplus default | 1268 (+673) | 36.98% | 1.707 | 19.585 | 25.495 |
gnssplusplus --enable-wide-lane-ar --wide-lane-threshold 0.10 |
818 (+223) | 33.65% | 0.799 (9 cm gap) | 19.971 | 26.429 |
PPC Tokyo + Nagoya need no Phase 2 flags. On Odaiba, --enable-wide-lane-ar --wide-lane-threshold 0.10 is the precision optimum.
| RTKLIB 2D | libgnss++ 2D |
|---|---|
![]() |
![]() |
The default RTK pipeline already dominates demo5 on the production datasets above. Five additional gates ship default-off for situations where you want to push further on precision-vs-fix-count tradeoffs. All are byte-identical to the default behavior unless explicitly enabled.
| Flag | Purpose | Default |
|---|---|---|
--ar-policy {extended|demo5-continuous} |
AR extras gate. demo5-continuous disables relaxed-hold-ratio / subset-fallback / hold-fix / Q-regularization for demo5-style continuous ambiguity tracking. |
extended |
--max-hold-div <m> |
Reject fix if the hold-state diverges from float by more than N meters. | 0 (disabled) |
--max-pos-jump <m> |
Reject fix if the epoch-to-epoch position jump exceeds N meters. | 0 (disabled) |
--max-consec-float-reset <N> |
Auto-reset ambiguities after N consecutive float epochs. | 0 (disabled) |
--max-postfix-rms <m> |
Reject fix if the L1 post-fix DD phase residual RMS exceeds N meters. | 0 (disabled) |
--enable-wide-lane-ar + --wide-lane-threshold <cycle> |
Pre-compute MW wide-lane integers and inject them as Kalman constraints into the LAMBDA search. Halves Hmed on Odaiba at the cost of ~35% Fix count. | false / 0.25 |
These were added in PR #19–#23. On PPC Tokyo and Nagoya the defaults already
win, so leave them off. On Odaiba (or other urban multipath sets),
--enable-wide-lane-ar --wide-lane-threshold 0.10 is the precision optimum.
- Public site: https://rsasaki0109.github.io/gnssplusplus-library/
- Documentation index
- Architecture notes
- Reference analyses
- Contribution workflow
Local docs site:
python3 -m pip install -r requirements-docs.txt
python3 -m mkdocs serveBuild the runtime image:
docker build -t libgnsspp:latest .Pull the published image:
docker pull ghcr.io/rsasaki0109/gnssplusplus-library:developRun the CLI against a mounted workspace or dataset directory:
docker run --rm -it \
-v "$PWD:/workspace" \
libgnsspp:latest \
solve --rover /workspace/data/rover_kinematic.obs \
--base /workspace/data/base_kinematic.obs \
--nav /workspace/data/navigation_kinematic.nav \
--out /workspace/output/docker_rtk.posServe the local web UI from inside the container:
docker run --rm -it \
-p 8085:8085 \
-v "$PWD:/workspace" \
libgnsspp:latest \
web --host 0.0.0.0 --port 8085 --root /workspaceThe image installs the gnss dispatcher, Python helpers, and libgnsspp Python package, but it does not embed the repo sample datasets. Mount your source tree or your own dataset directory.
Run the web UI with Compose:
docker compose up gnss-webOverride the image if you want a local or tagged build:
LIBGNSSPP_IMAGE=ghcr.io/rsasaki0109/gnssplusplus-library:v0.1.0 docker compose up gnss-web- Native solvers:
SPP,RTK,PPP,CLAS-style PPP - Native protocols:
RINEX,RTCM,UBX, directQZSS L6 - Raw/log tooling:
NMEA,NovAtel,SBP,SBF,Trimble,SkyTraq,BINEX - Product tooling:
fetch-products,ionex-info,dcb-info - Analysis tooling:
visibility,visibility-plot, andmoving-base-plotfor az/el/SNR exports plus moving-base/visibility PNG quick-looks - Moving-base tooling:
moving-base-prepareplusmoving-base-signofffor real bag/replay/live validation - One CLI entrypoint:
gnss spp,solve,ppp,visibility,stream,convert,live,rcv - Local web UI:
gnss webfor benchmark snapshots, live/moving-base/PPP-product sign-offs, 2D trajectories, visibility views, artifact bundles, receiver status, and artifact links - Built-in sign-off scripts and checked-in benchmark artifacts
- CMake install/export, Python bindings, and ROS2 playback node
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -jpython3 apps/gnss.py spp \
--obs data/rover_static.obs \
--nav data/navigation_static.nav \
--out output/spp_solution.pos
python3 apps/gnss.py solve \
--rover data/short_baseline/TSK200JPN_R_20240010000_01D_30S_MO.rnx \
--base data/short_baseline/TSKB00JPN_R_20240010000_01D_30S_MO.rnx \
--nav data/short_baseline/BRDC00IGS_R_20240010000_01D_MN.rnx \
--mode static \
--out output/rtk_solution.pos
python3 apps/gnss.py ppp \
--static \
--obs data/rover_static.obs \
--nav data/navigation_static.nav \
--out output/ppp_solution.pos
python3 apps/gnss.py visibility \
--obs data/rover_static.obs \
--nav data/navigation_static.nav \
--csv output/visibility.csv \
--summary-json output/visibility_summary.json \
--max-epochs 60
python3 apps/gnss.py replay \
--rover-rinex data/rover_kinematic.obs \
--base-rinex data/base_kinematic.obs \
--nav-rinex data/navigation_kinematic.nav \
--mode moving-base \
--out output/moving_base_replay.pos \
--max-epochs 20python3 apps/gnss.py ubx-info \
--input logs/session.ubx \
--decode-observations
python3 apps/gnss.py sbf-info \
--input logs/session.sbf \
--decode-pvt \
--decode-lband \
--decode-p2pp| Command | Purpose |
|---|---|
gnss spp |
Batch SPP from rover/nav RINEX |
gnss solve |
Batch RTK from rover/base/nav RINEX |
gnss ppp |
Batch PPP from rover RINEX plus nav or precise products |
gnss visibility |
Export azimuth/elevation/SNR visibility rows and summary JSON from rover/nav RINEX |
gnss visibility-plot |
Render a visibility CSV into a polar/elevation PNG quick-look |
gnss moving-base-plot |
Render a moving-base solution/reference pair into a baseline/heading PNG quick-look |
gnss fetch-products |
Fetch and cache SP3/CLK/IONEX/DCB files from local or remote sources |
gnss moving-base-prepare |
Extract rover/base UBX plus reference CSV from a ROS2 moving-base bag |
gnss scorpion-moving-base-signoff |
Prepare and validate the public SCORPION moving-base ROS2 bag through replay |
gnss stream |
Inspect and relay RTCM over file, NTRIP, TCP, or serial |
gnss convert |
Convert RTCM or UBX into simple RINEX outputs |
gnss ubx-info |
Inspect NAV-PVT, RAWX, SFRBX from file or serial |
gnss sbf-info |
Inspect Septentrio SBF PVTGeodetic, LBandTrackerStatus, P2PPStatus from file or serial |
gnss novatel-info |
Inspect NovAtel ASCII/Binary BESTPOS and BESTVEL logs |
gnss nmea-info |
Inspect GGA and RMC NMEA logs from file or serial |
gnss ionex-info |
Inspect IONEX header, map count, grid metadata, and auxiliary DCB blocks |
gnss dcb-info |
Inspect Bias-SINEX or auxiliary DCB product contents |
gnss qzss-l6-info |
Inspect direct QZSS L6 frames and export Compact SSR payloads |
gnss social-card |
Regenerate the Odaiba share image |
gnss short-baseline-signoff |
Static RTK sign-off |
gnss rtk-kinematic-signoff |
Kinematic RTK sign-off |
gnss ppp-static-signoff |
Static PPP sign-off |
gnss ppp-kinematic-signoff |
Kinematic PPP sign-off |
gnss ppp-products-signoff |
Static, kinematic, or PPC PPP sign-off with fetched SP3/CLK/IONEX/DCB products, optional MALIB delta gates, and comparison CSV/PNG artifacts |
gnss live-signoff |
Realtime/error-handling sign-off for recorded RTCM/UBX live inputs |
gnss ppc-demo |
External PPC-Dataset RTK/PPP verification against reference.csv |
gnss ppc-rtk-signoff |
Fixed RTK sign-off profiles for PPC Tokyo/Nagoya, with optional RTKLIB side-by-side gates |
gnss moving-base-signoff |
Real moving-base replay/live sign-off against per-epoch base/rover reference coordinates |
gnss odaiba-benchmark |
End-to-end Odaiba benchmark pipeline |
gnss web |
Local browser UI for summary JSON, live/moving-base/PPP-product sign-offs, .pos trajectories, moving-base/visibility plots and histories, receiver status, and artifact/provenance links |
See all commands:
python3 apps/gnss.py --helppython3 apps/gnss.py web \
--port 8085 \
--rcv-status output/receiver.status.jsonThen open http://127.0.0.1:8085 to inspect Odaiba metrics, live/moving-base/PPP-product sign-offs, 2D trajectories, moving-base and visibility plots, moving-base history, PPC summaries, receiver status, and linked artifact bundles in a browser. The PPP products table links directly to fetched products, MALIB .pos, comparison CSV/PNG artifacts, and dataset provenance.
Long-running dashboard commands can also read TOML config files. See
configs/web.example.toml, configs/live_signoff.example.toml,
configs/moving_base_signoff.example.toml, configs/ppc_rtk_signoff.example.toml,
and configs/ppp_products_ppc.example.toml, then pass --config-toml <file>.
Container form:
docker run --rm -it -p 8085:8085 -v "$PWD:/workspace" \
libgnsspp:latest web --host 0.0.0.0 --port 8085 --root /workspacegnss solve, gnss replay, and gnss live accept --mode moving-base. For real moving-base datasets, use gnss moving-base-signoff with a reference CSV carrying per-epoch base/rover ECEF coordinates. The repo does not ship a bundled moving-base dataset, so this command is intended for external real logs.
python3 apps/gnss.py moving-base-prepare \
--input /datasets/moving_base/2023-06-14T174658Z.zip \
--rover-ubx-out output/moving_base_rover.ubx \
--base-ubx-out output/moving_base_base.ubx \
--reference-csv output/moving_base_reference.csv \
--summary-json output/moving_base_prepare.json
python3 apps/gnss.py fetch-products \
--date 2023-06-14 \
--preset brdc-nav \
--summary-json output/moving_base_products.json
python3 apps/gnss.py moving-base-signoff \
--solver replay \
--rover-ubx output/moving_base_rover.ubx \
--base-ubx output/moving_base_base.ubx \
--nav-rinex ~/.cache/libgnsspp/products/nav/2023/165/BRDC00IGS_R_20231650000_01D_MN.rnx \
--reference-csv output/moving_base_reference.csv \
--summary-json output/moving_base_summary.json \
--require-fix-rate-min 90 \
--require-p95-baseline-error-max 1.0 \
--require-realtime-factor-min 1.0 \
--max-epochs 120
python3 apps/gnss.py scorpion-moving-base-signoff \
--input-url https://zenodo.org/api/records/8083431/files/2023-06-14T174658Z.zip/content \
--summary-json output/scorpion_moving_base_summary.json \
--require-matched-epochs-min 100 \
--require-fix-rate-min 80
python3 apps/gnss.py moving-base-signoff \
--config-toml configs/moving_base_signoff.example.toml
python3 apps/gnss.py live-signoff \
--config-toml configs/live_signoff.example.tomlpython3 apps/gnss.py fetch-products \
--date 2024-01-02 \
--preset igs-final \
--preset ionex \
--preset dcb \
--summary-json output/products.json
python3 apps/gnss.py ppp-static-signoff \
--fetch-products \
--product-date 2024-01-02 \
--product sp3=https://cddis.nasa.gov/archive/gnss/products/{gps_week}/COD0OPSFIN_{yyyy}{doy}0000_01D_05M_ORB.SP3.gz \
--product clk=https://cddis.nasa.gov/archive/gnss/products/{gps_week}/COD0OPSFIN_{yyyy}{doy}0000_01D_30S_CLK.CLK.gz \
--product ionex=https://cddis.nasa.gov/archive/gnss/products/ionex/{yyyy}/{doy}/COD0OPSFIN_{yyyy}{doy}0000_01D_01H_GIM.INX.gz \
--product dcb=https://cddis.nasa.gov/archive/gnss/products/bias/{yyyy}/CAS0MGXRAP_{yyyy}{doy}0000_01D_01D_DCB.BSX.gz \
--summary-json output/ppp_static_summary.json
python3 apps/gnss.py ppp-kinematic-signoff \
--max-epochs 120 \
--require-common-epoch-pairs-min 120 \
--require-reference-fix-rate-min 95 \
--require-converged \
--require-convergence-time-max 300 \
--require-mean-error-max 7 \
--require-p95-error-max 7 \
--require-max-error-max 7 \
--require-mean-sats-min 18 \
--require-ppp-solution-rate-min 100
python3 apps/gnss.py ppp-products-signoff \
--config-toml configs/ppp_products_ppc.example.toml
python3 apps/gnss.py ppc-rtk-signoff \
--config-toml configs/ppc_rtk_signoff.example.tomlDataset: UrbanNav Tokyo Odaiba (2018-12-19, Trimble rover/base, ~170 m baseline).
Comparison baseline: RTKLIB.
Current checked-in snapshot (kinematic, low-cost preset):
- RTKLIB demo5: Fix
595/ Rate7.22%/ Hmed0.707 m/ Hp9527.878 m/ Vp9545.212 m - libgnss++ default: Fix
1268(+673) / Rate36.98%/ Hmed1.707 m/ Hp9519.585 m/ Vp9525.495 m - libgnss++
--enable-wide-lane-ar --wide-lane-threshold 0.10: Fix818(+223) / Rate33.65%/ Hmed0.799 m(9 cm gap) / Hp9519.971 m/ Vp9526.429 m
libgnss++ dominates Fix count, Hp95, and Vp95 at any config; Hmed is the sole demo5 edge under default flags, and Phase 2 wide-lane AR closes it to 9 cm.
| RTKLIB 2D | libgnss++ 2D |
|---|---|
![]() |
![]() |
More artifacts:
- Full comparison figure
- Scorecard
- Summary JSON
- Optional side-by-side PPP benchmark path: JAXA-SNU/MALIB
- Additional low-cost GNSS RTK/PPP reference implementation: rtklibexplorer/RTKLIB
- Mixed-GNSS short-baseline RTK
- Mixed-GNSS kinematic RTK
- Static PPP
- Kinematic PPP
- CLAS-style PPP from compact sampled SSR and raw QZSS L6
PPC-Dataset can be verified directly from an extracted dataset tree:
python3 apps/gnss.py ppc-demo \
--dataset-root /datasets/PPC-Dataset \
--city tokyo \
--run run1 \
--solver rtk \
--require-realtime-factor-min 1.0 \
--summary-json output/ppc_tokyo_run1_rtk_summary.json
python3 apps/gnss.py ppc-rtk-signoff \
--dataset-root /datasets/PPC-Dataset \
--city tokyo \
--rtklib-bin /path/to/rnx2rtkp \
--summary-json output/ppc_tokyo_run1_rtk_signoff.jsonDataset source: taroz/PPC-Dataset
cmake --install build --prefix /opt/libgnssppInstalled layout includes:
bin/gnss- native binaries such as
gnss_spp,gnss_solve,gnss_ppp,gnss_stream - Python command wrappers and sign-off scripts
scripts/asset generatorslib/cmake/libgnsspp/libgnssppConfig.cmakelib/pkgconfig/libgnsspp.pc- Python package
libgnsspp
Examples:
# pkg-config
pkg-config --cflags --libs libgnsspp
# source the installed dispatcher
/opt/libgnsspp/bin/gnss social-card \
--lib-pos output/rtk_solution.pos \
--rtklib-pos output/driving_rtklib_rtk.pos \
--reference-csv data/driving/Tokyo_Data/Odaiba/reference.csv \
--output docs/driving_odaiba_social_card.pngPython bindings expose:
- RINEX header and epoch inspection
.posloading and solution statistics- coordinate conversion helpers
- file-based
SPP,PPP, andRTKsolve helpers
ROS2 support includes a playback node that publishes .pos files as:
sensor_msgs/NavSatFixgeometry_msgs/PoseStampednav_msgs/Path- solution status and satellite-count telemetry
Run the full non-GUI regression set:
ctest --test-dir build --output-on-failureImportant checks already covered in-tree:
- solver/unit tests
- live realtime/error-handling regression
- benchmark/image generation tests
- installed-prefix packaging smoke tests
- installed
gnss social-carddogfooding - installed feature-overview image generation
- Python bindings smoke tests
- ROS2 node smoke tests
Bundled samples live under:
data/data/short_baseline/data/driving/Tokyo_Data/Odaiba/
Generated benchmark outputs live under:
output/docs/
This repo is intentionally focused on a strong non-GUI GNSS stack.
It already covers:
- native
RTK,PPP,CLAS,RTCM,UBX,QZSS L6 - installed CLI tooling
- benchmarks, sign-off scripts, and README asset generation
It is still not marketed as a perfect RTKLIB drop-in replacement. The remaining gaps are about scope breadth, not the core non-GUI workflow shipped here.
MIT License. See LICENSE.





