Calculates position and time by using GNSS data
- Estimation of user position, velocity and time
- Calculates satellite position and velocity
- Precision estimation (GDOP)
- GPS (L1 C/A, L2C, L5, L1C), Galileo (E1B, E5a, E5b) and BeiDou (B1I, B3I, B1C, B2a, B2b), combined in one multi-GNSS solution
julia> ]
pkg> add PositionVelocityTimeDecoded data and code phase of satellite must be combined in the provided SatelliteState struct.
using PositionVelocityTime, GNSSSignals, GNSSDecoder
# decode satellite
gpsl1 = GPSL1CA()
sat_state = SatelliteState(
decoder = decoder,
system = gpsl1,
code_phase = code_phase,
carrier_doppler = carrier_doppler,
carrier_phase = carrier_phase # optional, in radians
)The declaration of carrier_phase is optional due to its small effect on the user position.
code_phase is in chips and carrier_phase in radians, matching Tracking's
get_code_phase and get_carrier_phase.
With Tracking.jl 9.1 or later loaded, a Tracking.TrackedSat can be passed to SatelliteState instead of
code_phase, carrier_doppler and carrier_phase — tracked_sat below is what
Tracking.get_sat_state returns for a tracked satellite:
using Tracking
# track and decode satellite
sat_state = SatelliteState(decoder, gpsl1, tracked_sat)Satellite states are handed over grouped by their ranging signal, one
PositionVelocityTime.SignalGroup per signal:
using PositionVelocityTime: SignalGroup
calc_pvt((
gps = SignalGroup(GPSL1CA(), gps_sat_states),
galileo = SignalGroup(GalileoE1B(), galileo_sat_states),
))provides a complete position calculation. A single group can be passed on its own, and
with Tracking.jl 9.1 or later loaded PositionVelocityTime.signal_groups(track_state, decoders) builds
a whole epoch's groups from a TrackState. Grouping is what keeps the solve type-stable
across constellations — see the migration note in the documentation if you are coming
from 5.x, where calc_pvt took a flat vector of satellite states directly.
A fix needs at least 4 healthy, fully decoded satellites (more for a multi-GNSS or multi-band set); when the epoch cannot be solved, the previous solution is returned unchanged instead of an error, so a receiver can pass whatever it currently tracks.
To solve epoch after epoch without allocating, use calc_pvt!, which reuses the
containers of the solution passed as its first argument and a PVTWorkspace for the
scratch buffers. PVTSolution is immutable, so keep the returned solution:
pvt = PVTSolution()
workspace = PVTWorkspace()
pvt = calc_pvt!(pvt, workspace, groups, pvt) # reuses only `pvt`; seeded from itselfThe estimated time pvt.time is a TAITime — whole TAI seconds since J2000 plus a
fraction. With AstroTime loaded, TAIEpoch(pvt.time) converts it exactly.
The solver compiles into a standalone executable with
JuliaC's juliac --trim=safe (Julia 1.12+);
test/trim holds such an app and a check that builds it and compares its output with a
regular Julia session.