Dual-frequency carrier-phase linear combinations and the precise-positioning prep tooling built on them: geometry-free and wide-lane phase, the narrow-lane code, Melbourne-Wubbena, cycle-slip detection, and Hatch carrier-smoothed code.
The numerical modeling lives in the Rust core. This module keeps the public Elixir API shape, validates options, normalizes arc maps for the NIF, and maps the core results back to the documented return maps.
Arc shape
detect_cycle_slips/2 and smooth_code/2 take an arc: a time-ordered list
of per-epoch maps for one satellite. Each epoch map is a convenient subset
of
%{
epoch: term(), # opaque, passed through to the output
phi1: float | nil, # band-1 carrier phase, cycles
phi2: float | nil, # band-2 carrier phase, cycles
p1: float | nil, # band-1 code, metres
p2: float | nil, # band-2 code, metres
lli1: integer | nil, # band-1 LLI (bit 0 = loss of lock)
lli2: integer | nil, # band-2 LLI
f1: float | nil, # band-1 carrier frequency, Hz (nil => skip)
f2: float | nil # band-2 carrier frequency, Hz
}An epoch with an unknown band frequency is skipped and reported with
skipped: true. epoch is passed through unchanged. The data-gap detector
can compare numeric-second epochs and NaiveDateTime epochs; other epoch
terms remain opaque and do not trigger :data_gap.
Summary
Functions
Code-minus-carrier diagnostic CMC = P - L (metres).
Detect cycle slips on a single-satellite arc.
Geometry-free phase combination L_GF = l1_m - l2_m (metres).
Melbourne-Wubbena combination (metres).
Narrow-lane code P_NL = (f1*p1 + f2*p2) / (f1 + f2) (metres).
Carrier phase in metres, L = c / f * phi.
Single-frequency Hatch carrier-smoothed code on band 1.
Dual-frequency ionosphere-free Hatch carrier-smoothed code.
Wide-lane wavelength lambda_WL = c / (f1 - f2) (metres).
Types
@type slip_reason() :: :lli | :geometry_free | :melbourne_wubbena | :data_gap
@type smooth_result() :: %{ epoch: term(), p_smooth: float() | nil, window: non_neg_integer(), reset: boolean() }
Functions
@spec code_minus_carrier(number(), number(), number()) :: {:ok, float()} | {:error, :invalid_frequency}
Code-minus-carrier diagnostic CMC = P - L (metres).
@spec detect_cycle_slips( [epoch_map()], keyword() ) :: [slip_result()]
Detect cycle slips on a single-satellite arc.
Returns one output map per input epoch:
%{epoch: term(), slip: boolean(), reasons: [reason],
gf: float | nil, mw: float | nil, skipped: boolean()}with reason in [:lli, :geometry_free, :melbourne_wubbena, :data_gap].
Options:
:gf_threshold_m(default0.05):mw_threshold_cycles(default4.0):min_arc_gap_s(default300.0)
Thresholds must be non-negative numbers; invalid values raise
ArgumentError.
Geometry-free phase combination L_GF = l1_m - l2_m (metres).
@spec melbourne_wubbena(number(), number(), number(), number(), number(), number()) :: {:ok, float()} | {:error, :equal_frequencies}
Melbourne-Wubbena combination (metres).
MW = L_WL - P_NL
= lambda_WL*(phi1 - phi2) - (f1*P1 + f2*P2)/(f1 + f2)Sign convention: wide-lane phase minus narrow-lane code.
@spec narrow_lane_code(number(), number(), number(), number()) :: {:ok, float()} | {:error, :equal_frequencies}
Narrow-lane code P_NL = (f1*p1 + f2*p2) / (f1 + f2) (metres).
Carrier phase in metres, L = c / f * phi.
phi_cyc is carrier phase in cycles and f_hz is the carrier frequency in
hertz. Returns {:ok, l_m} or {:error, :invalid_frequency} when the
frequency is not positive. Never raises.
@spec smooth_code( [epoch_map()], keyword() ) :: [smooth_result()]
Single-frequency Hatch carrier-smoothed code on band 1.
The filter resets on detected cycle slips, LLI loss of lock, data gaps, or
missing band-1 code/phase/frequency. :hatch_window_cap defaults to
100 and must be a positive integer.
@spec smooth_iono_free_code( [epoch_map()], keyword() ) :: [iono_free_smooth_result()]
Dual-frequency ionosphere-free Hatch carrier-smoothed code.
Forms ionosphere-free code and carrier phase at each epoch in the Rust core,
then applies the same Hatch recursion and reset policy as smooth_code/2.
Wide-lane wavelength lambda_WL = c / (f1 - f2) (metres).
Returns {:ok, lambda_WL} or {:error, :equal_frequencies} when the two
band frequencies are equal within the core epsilon.