Orbis.GNSS.CarrierPhase (Orbis v0.30.0)

Copy Markdown View Source

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

epoch_map()

@type epoch_map() :: %{optional(atom()) => term()}

iono_free_smooth_result()

@type iono_free_smooth_result() :: %{
  epoch: term(),
  p_smooth: float() | nil,
  p_if: float() | nil,
  l_if: float() | nil,
  window: non_neg_integer(),
  reset: boolean()
}

slip_reason()

@type slip_reason() :: :lli | :geometry_free | :melbourne_wubbena | :data_gap

slip_result()

@type slip_result() :: %{
  epoch: term(),
  slip: boolean(),
  reasons: [slip_reason()],
  gf: float() | nil,
  mw: float() | nil,
  skipped: boolean()
}

smooth_result()

@type smooth_result() :: %{
  epoch: term(),
  p_smooth: float() | nil,
  window: non_neg_integer(),
  reset: boolean()
}

Functions

code_minus_carrier(p_m, phi_cyc, f_hz)

@spec code_minus_carrier(number(), number(), number()) ::
  {:ok, float()} | {:error, :invalid_frequency}

Code-minus-carrier diagnostic CMC = P - L (metres).

detect_cycle_slips(arc, opts \\ [])

@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 (default 0.05)
  • :mw_threshold_cycles (default 4.0)
  • :min_arc_gap_s (default 300.0)

Thresholds must be non-negative numbers; invalid values raise ArgumentError.

geometry_free(l1_m, l2_m)

@spec geometry_free(number(), number()) :: float()

Geometry-free phase combination L_GF = l1_m - l2_m (metres).

melbourne_wubbena(phi1_cyc, phi2_cyc, p1_m, p2_m, f1, f2)

@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.

narrow_lane_code(p1_m, p2_m, f1, f2)

@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).

phase_meters(phi_cyc, f_hz)

@spec phase_meters(number(), number()) ::
  {:ok, float()} | {:error, :invalid_frequency}

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.

smooth_code(arc, opts \\ [])

@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.

smooth_iono_free_code(arc, opts \\ [])

@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(f1, f2)

@spec wide_lane_wavelength(number(), number()) ::
  {:ok, float()} | {:error, :equal_frequencies}

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.