geoveil-mpv1.0.1
GNSS code multipath · Rust core · Python API

Measure multipath. Map where it comes from.

geoveil-mp reads RINEX 2, 3 and 4 and estimates code multipath for every pseudorange signal on GPS, GLONASS, Galileo, BeiDou, QZSS and NavIC, with cycle slips detected per carrier. Version 1.0 adds SNR wavelet analysis and first Fresnel zone mapping, so the numbers point back to the ground that caused them.

$pip install geoveil-mp
20×faster SNR wavelets
1.8e-15max difference vs numpy
10new Python functions

First Fresnel zone explorer

eq. 6 · Hunegnaw & Teferle, Sensors 2022
Carrier
Semi-major a
4.896 m
Semi-minor b
1.267 m
Specular distance d
7.464 m
<15° 15–30° 30–60° >60°
gm.fresnel_zone(15.0, 2.0) # → (4.896, 1.267, 7.464)

b = √(λh / sin θ + (λ / 2 sin θ)²), a = b / sin θ, d = h / tan θ. Satellite tracks are illustrative; the geometry is the library's.

Release1.0.1 · stable

What 1.0 adds

The wavelet and Fresnel analysis that the GeoVeil batch platform ran in numpy now lives in the library, in Rust, with the Python API you already use. Everything from 0.2 works as before.

SNR residuals

Wavelet spectra per satellite

SNR is converted from dB-Hz to linear amplitude, detrended arc by arc, and passed through a Morlet transform. You get band power in periods matched to your sampling interval and a 95 % red-noise significance level.

analyze_snr_waveletsscalogramband_power
Reflector geometry

First Fresnel zones

For every 10° × 10° cell a satellite crosses, the library returns the zone's semi-axes and its distance from the antenna, for your antenna height and carrier. Large multipath now has an address on the ground.

fresnel_zonefresnel_map
Parity

Same numbers, 20× faster

Each function was checked against the numpy reference on synthetic series and on a real station hour. Satellites run in parallel and the GIL is released, so thread pools keep working.

morlet_cwtdetrend_arcsplit_arcs
Measured onnumpy referencegeoveil-mp 1.0
SNR wavelets, 51 satellites, 3,600 epochs at 1 Hz7.05 s0.35 s
Largest relative difference in wavelet powerreference1.8 × 10⁻¹⁵
Largest relative difference in detrended residualsreference4.3 × 10⁻¹²
Linux container, release build, same RINEX 3.04 file for both. Parity script and results are in the 1.0.0 pull request.
In productionHAR1 · 2025-12-31 · 30 s

Running on batch.geoveil-rinex.eu

The GeoVeil batch platform runs this analysis on every station day it receives. These screenshots show one day of the HAR1 station, a daily 30 s RINEX file. Select a chart to see it full size.

MethodAnubis / TEQC style

From observables to reflectors

Two processing lanes share one parsed file. The code lane measures how much multipath each signal carries. The SNR lane finds its periods and the Fresnel step places it around the antenna.

MPk = Pk − (1 + 2/(α−1)) Φi + (2/(α−1)) Φj,   α = (fi / fj)² Φi is the phase on the code's own band, Φj a partner band from a fixed per-system priority list. GLONASS uses each satellite's channel number from the header.

Code multipath

  1. Parse RINEX 2, 3 or 4 into code, phase and SNR per satellite and epoch.
  2. Form MPk for every pseudorange code with a deterministic phase pairing.
  3. Detect cycle slips with geometry-free, code-phase and loss-of-lock tests whose thresholds scale with the interval.
  4. Remove the ambiguity bias per arc, resetting at gaps, slips and jumps.
  5. Report per signal: RMS, elevation-weighted RMS and slip counts, with elevations from SP3 or broadcast orbits.

SNR and reflectors new

  1. Pick the band-1 S observable for each satellite.
  2. Convert dB-Hz to linear amplitude and split into arcs at gaps.
  3. Detrend each arc with the polynomial of order 2 to 9 that leaves the smallest residual.
  4. Run the Morlet transform and average power into four period bands, with red-noise significance.
  5. Place first Fresnel zones along each track for your antenna height and carrier.
ConstellationBest signalRMSWorst signalRMS
GalileoE5 AltBOC (M8X)0.165 mE5b (M7X)0.395 m
GPSL2C (M2X)0.312 mL1 C/A (M1C)0.342 m
BeiDouB2I (M7I)0.295 mB3I (M6I)0.354 m
GLONASSL2 P (M2P)0.318 mL1 C/A (M1C)0.566 m
24 h of BOR1 (EPN, Poland), 30 s multi-GNSS RINEX 3, default settings. The ranking reproduces published station-monitoring results.
APIPython 3.9+ · Rust 1.75+

Five lines to a station report

Install from PyPI, point it at a RINEX file, and read plain Python lists and dicts back. The Rust crate exposes the same functions.

import geoveil_mp as gm

obs = gm.read_rinex_obs("HAR100ROU_R_20253650000_01D_30S_MO.rnx")

# Code multipath and cycle slips per signal
res = gm.MultipathAnalyzer(obs, 10.0, ["G", "R", "E"]).analyze()
for s in res.statistics:
    print(s.signal, round(s.rms, 3), s.cycle_slips)   # GLOM3Q 0.395 ...

# New in 1.0: SNR wavelets for every satellite, in parallel
w = gm.analyze_snr_wavelets(obs)
w["bands"]                       # [[60, 120], [120, 300], [300, 900], [900, 1800]]
w["band_power"]["R02"]["power"]  # binned power, one series per band

r = w["residuals"]["R02"]
scal = gm.scalogram(r["t"], r["v"], w["interval"])   # power, significance, coi

# First Fresnel zone for a 2 m antenna at 15° elevation, GPS L1
a, b, dist = gm.fresnel_zone(15.0, 2.0)                    # (4.896, 1.267, 7.464) m
New in 1.0Returns
analyze_snr_wavelets(obs)Residuals and binned band power per satellite, in the layout the platform stores
scalogram(t, values, interval)Power matrix, periods, 95 % significance and cone of influence for the longest arc
morlet_cwt(y, dt)Wavelet power, scales, Fourier periods and cone of influence
band_power(y, dt, bands)Scale-averaged power for each (low, high) period band
red_noise_significance(y, dt, periods)Lag-1 red-noise significance level per period
period_bands(dt)The four default bands for a sampling interval
detrend_arc(t, values)Residual and the chosen polynomial order
split_arcs(times, dt)(start, stop) index pairs of continuous arcs
fresnel_zone(elevation, height)Semi-major axis, semi-minor axis and specular distance in metres
fresnel_map(tracks, height)Zone footprints along satellite tracks, one per 10° × 10° cell
LicensePolyForm Noncommercial 1.0.0

Free for science. Licensed for business.

From 1.0, geoveil-mp is source-available under PolyForm Noncommercial 1.0.0 with an attribution term.

Non-commercial use

Free, with credit

  • Research, teaching and personal projects.
  • Universities, public research bodies, charities and government agencies.
  • Credit "geoveil-mp by Miluta Dulea-Flueras" with a link to the repository, and cite it in publications.

Versions 0.2.1 and earlier remain available under the MIT license. The full terms are in the LICENSE file.

CitationBibTeX
geoveil_mp.bib
@software{geoveil_mp,
  author  = {Dulea-Flueras, Miluta},
  title   = {geoveil-mp: GNSS Code Multipath Analysis Library},
  year    = {2026},
  version = {1.0.1},
  url     = {https://github.com/miluta7/geoveil-mp},
  license = {PolyForm-Noncommercial-1.0.0}
}

Method: Hunegnaw, A.; Teferle, F.N. Evaluation of the Multipath Environment Using Electromagnetic-Absorbing Materials at Continuous GNSS Stations. Sensors 2022, 22, 3384. Wavelets: Torrence, C.; Compo, G.P. A Practical Guide to Wavelet Analysis. Bull. Amer. Meteor. Soc. 1998, 79, 61–78.