The dual-frequency ionosphere-free linear combination of pseudoranges.
The first-order ionospheric group delay on a GNSS pseudorange is dispersive: to
first order it scales as 1 / f^2, so a signal at carrier f is delayed by
I(f) = K / f^2 for a slant-path constant K proportional to the total
electron content (TEC). Measuring the same range on two carriers f1 and f2
therefore gives two observations that share the geometry but carry different
ionospheric delays, and a fixed linear combination of the two cancels the
1 / f^2 term exactly:
PR_IF = (f1^2 * PR1 - f2^2 * PR2) / (f1^2 - f2^2)Writing gamma = f1^2 / (f1^2 - f2^2) this is the affine combination
PR_IF = gamma * PR1 - (gamma - 1) * PR2Substituting PR_i = R + K / f_i^2 (a true range R plus the first-order
ionospheric delay on band i) the K terms cancel and PR_IF = R. A position
solve fed these combined pseudoranges therefore needs no ionosphere model — call
Orbis.GNSS.Positioning.solve/4 with ionosphere: false (the troposphere term,
which is non-dispersive and does not cancel, still applies).
Frequency table
The standard carrier frequencies used here, in hertz:
| System | Band | Frequency (MHz) |
|---|---|---|
| GPS | L1 | 1575.42 |
| GPS | L2 | 1227.60 |
| Galileo | E1 | 1575.42 |
| Galileo | E5a | 1176.45 |
| BeiDou | B1I | 1561.098 |
| BeiDou | B3I | 1268.52 |
The default combination pair per system is GPS {:l1, :l2}, Galileo
{:e1, :e5a}, BeiDou {:b1i, :b3i}.
Noise amplification
The combination is not free: because it is a weighted difference of two noisy
observations, uncorrelated band noise of equal standard deviation sigma is
amplified to sigma * sqrt(gamma^2 + (gamma - 1)^2). For GPS L1/L2 this factor
is about 2.978; for Galileo E1/E5a about 2.588. See noise_amplification/2.
Non-goals
This module builds only the first-order ionosphere-free pseudorange combination. It deliberately does not implement carrier-phase combinations, the Melbourne-Wubbena / wide-lane / geometry-free combinations, ambiguity resolution, the second-order ionospheric term, or triple-frequency combinations.
Summary
Types
A reason a satellite was dropped from the paired set.
A pseudorange observation {satellite_id, range_m}.
Functions
The standard combination band pair for system.
The full carrier-frequency table as %{system => %{band => f_hz}}.
The carrier frequency in hertz for system ("G", "E", "C") and band.
The ionosphere-free combination coefficient gamma = f1^2 / (f1^2 - f2^2).
The ionosphere-free pseudorange from two carrier-band pseudoranges.
Convenience: pull two carrier bands from a parsed observation handle and combine them into ionosphere-free pseudoranges for one epoch.
Combine two per-satellite pseudorange bands into ionosphere-free pseudoranges.
The noise-amplification factor sqrt(gamma^2 + (gamma - 1)^2).
Types
Functions
The standard combination band pair for system.
Returns {:ok, {band1, band2}} or {:error, {:unknown_system, system}}.
The full carrier-frequency table as %{system => %{band => f_hz}}.
@spec frequency(String.t(), atom()) :: {:ok, float()} | {:error, {:unknown_band, String.t(), atom()}}
The carrier frequency in hertz for system ("G", "E", "C") and band.
Returns {:ok, f_hz} or {:error, {:unknown_band, system, band}}.
The ionosphere-free combination coefficient gamma = f1^2 / (f1^2 - f2^2).
Returns {:error, :equal_frequencies} when f1 == f2 (the combination is
undefined; the denominator vanishes). Never raises.
@spec iono_free(float(), float(), float(), float()) :: {:ok, float()} | {:error, :equal_frequencies}
The ionosphere-free pseudorange from two carrier-band pseudoranges.
PR_IF = (f1^2 * pr1 - f2^2 * pr2) / (f1^2 - f2^2)
= gamma * pr1 - (gamma - 1) * pr2pr1/pr2 are in metres on carriers f1/f2 (hertz). Returns {:ok, pr_if},
or {:error, :equal_frequencies} when f1 == f2. Never raises.
@spec iono_free_from_obs( Orbis.GNSS.RINEX.Observations.t(), non_neg_integer() | tuple(), keyword() ) :: {:ok, {[observation()], [{String.t(), drop_reason()}]}} | {:error, term()}
Convenience: pull two carrier bands from a parsed observation handle and combine them into ionosphere-free pseudoranges for one epoch.
Calls Orbis.GNSS.RINEX.Observations.pseudoranges/3 twice — once for each band's code
preference — then iono_free_pseudoranges/3. epoch is an epoch index or
{{y, mo, d}, {h, mi, s}} tuple, exactly as Observations.pseudoranges/3 accepts.
Returns {:ok, {combined, dropped}} or {:error, reason} (propagated from
either extraction).
Options
:codes— a map%{system => {band1_codes, band2_codes}}of the observation codes to extract for each band, e.g.%{"G" => {["C1C"], ["C2W", "C2L"]}, "E" => {["C1C"], ["C5Q"]}}. When omitted, the standard band-1/band-2 codes for the systems present in the file'sobservation_codes/1are used (GPS C1C/C2W, Galileo C1C/C5Q, BeiDou C2I/C6I), restricted to those whose codes the file actually carries.:pairs— forwarded toiono_free_pseudoranges/3.
@spec iono_free_pseudoranges([observation()], [observation()], keyword()) :: {[observation()], [{String.t(), drop_reason()}]}
Combine two per-satellite pseudorange bands into ionosphere-free pseudoranges.
band1 and band2 are [{satellite_id, range_m}] lists for the two carriers.
Satellites are paired by id, and each pair is combined with the frequency pair
for that satellite's system (the leading letter of the id), so a mixed
GPS+Galileo+BeiDou set uses each system's own carriers.
Returns {combined, dropped} where combined is the ascending-by-id list of
{satellite_id, pr_if} and dropped reports every satellite that could not be
combined as {satellite_id, reason}:
:missing_band2— present inband1only;:missing_band1— present inband2only;:duplicate_observation— the satellite appears more than once in a band, so which pseudorange to use is ambiguous; it is dropped rather than silently collapsed to whichever entry comes last;:unknown_system— the system letter has no known frequency pair.
Empty input yields {[], []}. Never raises.
Options
:pairs— override the band pair per system, e.g.pairs: %{"G" => {:l1, :l2}}. A system without an override uses its standard default pair.
The noise-amplification factor sqrt(gamma^2 + (gamma - 1)^2).
This is the factor by which uncorrelated equal-variance band noise is amplified
into the combined pseudorange. About 2.978 for GPS L1/L2 and 2.588 for
Galileo E1/E5a. Returns {:error, :equal_frequencies} when f1 == f2. Never
raises.