Sidereon.GNSS.RTK (Sidereon v0.12.0)

Copy Markdown View Source

RTK-facing carrier/code double-difference primitives.

A base receiver and a rover receiver observing the same satellites have receiver-clock terms that differ by station but are common to every satellite. A single difference subtracts base from rover for the same satellite; a double difference subtracts a reference satellite's single difference:

DD_s = (rover_s - base_s) - (rover_ref - base_ref)

The receiver clocks cancel in the second subtraction. Satellite-clock, ephemeris, and short-baseline atmosphere errors that are common between base and rover also cancel in the receiver single difference. The remaining carrier-phase double differences are the measurement surface used by RTK baseline estimation and integer ambiguity fixing.

double_differences/3 returns normalized measurements. solve_rtk_float/1 and solve_rtk_fixed/1 operate on already prepared RTK epochs.

Example

iex> base = [
...>   {"G01", 20_100.0, 20_103.0},
...>   {"G02", 21_105.0, 21_110.0}
...> ]
iex> rover = [
...>   {"G01", 20_040.0, 20_044.0},
...>   {"G02", 21_060.0, 21_066.0}
...> ]
iex> {:ok, result} = Sidereon.GNSS.RTK.double_differences(base, rover, reference_satellite_id: "G01")
iex> result.double_differences
[%{satellite_id: "G02", reference_satellite_id: "G01", ambiguity_id: "G02", code_m: 15.0, phase_m: 17.0}]

Summary

Types

One RTK epoch carrying paired base/rover observations and satellite positions.

One non-reference satellite's double-difference observation.

One RTK epoch carrying raw dual-frequency base/rover observations.

Raw dual-frequency code/carrier observation for wide-lane/narrow-lane RTK.

ECEF position in metres.

Code and carrier-phase observation in metres.

Double-difference result with deterministic satellite ordering.

Satellite ECEF position keyed by satellite id.

Functions

Build code and carrier-phase double differences from base and rover observations.

Fix wide-lane RTK arc ambiguities by delegating to the core arc helper.

Build ionosphere-free RTK arc epochs from dual-frequency epochs and fixed wide-lane integers.

Solve a sequential RTK baseline arc from raw rover+base epochs, delegating the whole driver (epoch normalization, reference selection, sequential filter, and per-epoch ambiguity search) to the sidereon-core solve_rtk_arc kernel.

Solve a static integer-fixed RTK baseline from normalized RTK epochs.

Solve a static float RTK baseline from normalized RTK epochs.

Solve a static RTK arc with a typed core-style configuration map.

Types

baseline_epoch()

@type baseline_epoch() :: %{
  :base_observations => [observation()],
  :rover_observations => [observation()],
  :satellite_positions_m => satellite_positions(),
  optional(:base_satellite_positions_m) => satellite_positions(),
  optional(:rover_satellite_positions_m) => satellite_positions(),
  optional(:velocity_mps) => ecef_input(),
  optional(:epoch) => term()
}

One RTK epoch carrying paired base/rover observations and satellite positions.

:epoch is preserved in residual diagnostics; it is not interpreted by this first solver layer because satellite positions are supplied by the caller. :satellite_positions_m is used for satellite selection and elevation weighting. When the caller has receiver-specific transmit-time positions, it may also provide :base_satellite_positions_m and :rover_satellite_positions_m; otherwise both default to :satellite_positions_m.

double_difference()

@type double_difference() :: %{
  satellite_id: String.t(),
  reference_satellite_id: String.t(),
  ambiguity_id: String.t(),
  code_m: float(),
  phase_m: float()
}

One non-reference satellite's double-difference observation.

dual_frequency_baseline_epoch()

@type dual_frequency_baseline_epoch() :: %{
  :base_observations => [dual_frequency_observation()],
  :rover_observations => [dual_frequency_observation()],
  :satellite_positions_m => satellite_positions(),
  optional(:base_satellite_positions_m) => satellite_positions(),
  optional(:rover_satellite_positions_m) => satellite_positions(),
  optional(:epoch) => term()
}

One RTK epoch carrying raw dual-frequency base/rover observations.

dual_frequency_observation()

@type dual_frequency_observation() :: %{
  :satellite_id => String.t(),
  :p1_m => number(),
  :p2_m => number(),
  :phi1_cyc => number(),
  :phi2_cyc => number(),
  :f1_hz => number(),
  :f2_hz => number(),
  optional(:ambiguity_id) => String.t(),
  optional(:lli1) => integer() | nil,
  optional(:lli2) => integer() | nil
}

Raw dual-frequency code/carrier observation for wide-lane/narrow-lane RTK.

p1_m / p2_m are code pseudoranges in metres, phi1_cyc / phi2_cyc are carrier phases in cycles, and f1_hz / f2_hz are the corresponding carrier frequencies. :ambiguity_id is normally omitted; the wide-lane solver sets it internally when :on_cycle_slip is :split_arc.

ecef_input()

@type ecef_input() ::
  {number(), number(), number()}
  | %{x_m: number(), y_m: number(), z_m: number()}

ECEF position in metres.

observation()

@type observation() ::
  %{
    :satellite_id => String.t(),
    :code_m => number(),
    :phase_m => number(),
    optional(:ambiguity_id) => String.t(),
    optional(:lli) => integer() | nil,
    optional(:loss_of_lock_indicator) => integer() | nil
  }
  | {String.t(), number(), number()}

Code and carrier-phase observation in metres.

Map observations may optionally carry :ambiguity_id to identify a carrier arc and :lli (or :loss_of_lock_indicator) for single-frequency loss-of-lock handling. Tuple observations use the satellite id as the ambiguity id and have no LLI.

result()

@type result() :: %{
  reference_satellite_id: String.t(),
  double_differences: [double_difference()],
  dropped_sats: [String.t()]
}

Double-difference result with deterministic satellite ordering.

satellite_positions()

@type satellite_positions() :: %{required(String.t()) => ecef_input()}

Satellite ECEF position keyed by satellite id.

Functions

double_differences(base_observations, rover_observations, opts \\ [])

@spec double_differences([observation()], [observation()], keyword()) ::
  {:ok, result()} | {:error, term()}

Build code and carrier-phase double differences from base and rover observations.

Observations can be maps with :satellite_id, :code_m, and :phase_m, or {satellite_id, code_m, phase_m} tuples. Satellites are paired by id; any satellite not present at both receivers is reported in :dropped_sats.

Options:

  • :reference_satellite_id - reference satellite for the second difference: a satellite id binary (single-system data only) or a per-system map covering every observed system. When omitted, each system's lexicographically first common satellite is selected deterministically. Non-reference satellites difference against their own system's reference.

Returns {:ok, result} or a tagged error. At least two common satellites are required so one non-reference double difference can be produced.

fix_wide_lane_rtk_arc(epochs, config)

@spec fix_wide_lane_rtk_arc([dual_frequency_baseline_epoch()], map()) ::
  {:ok, map()} | {:error, term()}

Fix wide-lane RTK arc ambiguities by delegating to the core arc helper.

prepare_ionosphere_free_rtk_arc(epochs, wide_lane_cycles, config)

@spec prepare_ionosphere_free_rtk_arc(
  [dual_frequency_baseline_epoch()],
  %{required(String.t()) => integer()},
  map()
) :: {:ok, map()} | {:error, term()}

Build ionosphere-free RTK arc epochs from dual-frequency epochs and fixed wide-lane integers.

solve_arc(epochs, config)

@spec solve_arc([map()], map()) :: {:ok, map()} | {:error, term()}

Solve a sequential RTK baseline arc from raw rover+base epochs, delegating the whole driver (epoch normalization, reference selection, sequential filter, and per-epoch ambiguity search) to the sidereon-core solve_rtk_arc kernel.

This is a thin delegation to the core arc driver. epochs is a list of raw epoch maps:

  • :base, :rover - lists of observation maps %{satellite_id:, ambiguity_id:, code_m:, phase_m:}
  • :satellite_positions_m - %{satellite_id => {x, y, z}} shared-position map
  • :base_satellite_positions_m, :rover_satellite_positions_m - optional per-receiver transmit-time position maps (default to the shared map)
  • :velocity_mps - optional rover ECEF velocity {vx, vy, vz}
  • :prediction_time_s - optional epoch time coordinate

config is a map:

  • :base_m - base station ECEF {x, y, z}
  • :reference - :auto (default), {:satellite, id}, or {:per_system, %{letter => id}}
  • :model - %{code_sigma_m:, phase_sigma_m:, stochastic_model:, elevation_weighting?:, sagnac?:}
  • :baseline_prior_sigma_m, :ambiguity_prior_sigma_m
  • :initial_baseline_m - {x, y, z} (default {0, 0, 0})
  • :wavelengths_m, :offsets_m - %{ambiguity_id => value}
  • :update_opts - the per-epoch update controls (see arc_update_opts)

Returns {:ok, solution} with :references, per-epoch :epochs, and the carried :final_state, or {:error, reason}.

solve_rtk_fixed(config)

@spec solve_rtk_fixed(map()) ::
  {:ok, Sidereon.GNSS.RTK.FixedBaselineSolution.t()} | {:error, term()}

Solve a static integer-fixed RTK baseline from normalized RTK epochs.

solve_rtk_float(config)

@spec solve_rtk_float(map()) ::
  {:ok, Sidereon.GNSS.RTK.FloatBaselineSolution.t()} | {:error, term()}

Solve a static float RTK baseline from normalized RTK epochs.

solve_static_arc(epochs, config)

@spec solve_static_arc([map()], map()) :: {:ok, map()} | {:error, term()}

Solve a static RTK arc with a typed core-style configuration map.