sidereon_core/sbas_pl/
mod.rs1pub mod error_model;
8
9pub use crate::araim::{AraimGeometry as ProtectionGeometry, AraimRow as ProtectionRow};
10pub use error_model::{
11 give_variance_m2_for_givei, sbas_obliquity_factor, sigma_air_multipath_m,
12 sigma_flt_m_for_udrei, sigma_tropo_m, udre_variance_m2_for_udrei, AirborneModel,
13 DegradationParams, SbasErrorModel, SbasSisError, SBAS_IONOSPHERE_SHELL_HEIGHT_KM,
14};
15
16use crate::araim::protection::gain_matrix_enu;
17use crate::araim::{AraimError, ProtectionModel};
18use crate::integrity::{error_ellipse_2x2_unit, metric_cross, metric_sigma, IntegrityError};
19
20#[derive(Debug, Clone, Copy, PartialEq)]
22pub struct SbasKMultipliers {
23 pub k_h: f64,
25 pub k_v: f64,
27}
28
29impl SbasKMultipliers {
30 pub const PRECISION_APPROACH: Self = Self {
32 k_h: 6.0,
33 k_v: 5.33,
34 };
35
36 pub const EN_ROUTE_NPA: Self = Self {
38 k_h: 6.18,
39 k_v: 5.33,
40 };
41}
42
43#[derive(Debug, Clone, Copy, PartialEq)]
45pub struct SbasProtection {
46 pub hpl_m: f64,
48 pub vpl_m: f64,
50 pub d_major_m: f64,
52 pub sigma_u_m: f64,
54 pub d_east_m: f64,
56 pub d_north_m: f64,
58 pub d_en_m2: f64,
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
64pub enum SbasPlError {
65 #[error("insufficient SBAS protection-level geometry")]
67 InsufficientGeometry,
68 #[error("SBAS protection-level numerical failure")]
70 NumericalFailure,
71 #[error("invalid SBAS protection-level error model")]
73 InvalidErrorModel,
74}
75
76pub fn sbas_protection_levels(
84 geometry: &ProtectionGeometry,
85 model: &dyn ProtectionModel,
86 k: SbasKMultipliers,
87) -> Result<SbasProtection, SbasPlError> {
88 validate_k(k)?;
89 if geometry.rows.is_empty() {
90 return Err(SbasPlError::InsufficientGeometry);
91 }
92
93 let mut sigmas_m = Vec::with_capacity(geometry.rows.len());
94 for row in &geometry.rows {
95 let sigma_m = model.sigma_int_m(row).map_err(map_model_error)?;
96 if !valid_positive_finite(sigma_m) {
97 return Err(SbasPlError::InvalidErrorModel);
98 }
99 sigmas_m.push(sigma_m);
100 }
101 let weights = sigmas_m
102 .iter()
103 .map(|sigma_m| 1.0 / (sigma_m * sigma_m))
104 .collect::<Vec<_>>();
105 let gain = gain_matrix_enu(geometry, &weights).map_err(map_araim_error)?;
106
107 let d_east_m = metric_sigma(&gain.enu_rows[0], &sigmas_m);
108 let d_north_m = metric_sigma(&gain.enu_rows[1], &sigmas_m);
109 let sigma_u_m = metric_sigma(&gain.enu_rows[2], &sigmas_m);
110 let d_en_m2 = metric_cross(&gain.enu_rows[0], &gain.enu_rows[1], &sigmas_m);
111 if [d_east_m, d_north_m, sigma_u_m, d_en_m2]
112 .iter()
113 .any(|value| !value.is_finite())
114 {
115 return Err(SbasPlError::NumericalFailure);
116 }
117
118 let ellipse = error_ellipse_2x2_unit([
119 [d_east_m * d_east_m, d_en_m2],
120 [d_en_m2, d_north_m * d_north_m],
121 ])
122 .map_err(map_integrity_error)?;
123 let d_major_m = ellipse.semi_major;
124 Ok(SbasProtection {
125 hpl_m: k.k_h * d_major_m,
126 vpl_m: k.k_v * sigma_u_m,
127 d_major_m,
128 sigma_u_m,
129 d_east_m,
130 d_north_m,
131 d_en_m2,
132 })
133}
134
135fn validate_k(k: SbasKMultipliers) -> Result<(), SbasPlError> {
136 if valid_positive_finite(k.k_h) && valid_positive_finite(k.k_v) {
137 Ok(())
138 } else {
139 Err(SbasPlError::InvalidErrorModel)
140 }
141}
142
143fn valid_positive_finite(value: f64) -> bool {
144 value.is_finite() && value > 0.0
145}
146
147fn map_model_error(error: AraimError) -> SbasPlError {
148 match error {
149 AraimError::InsufficientGeometry => SbasPlError::InsufficientGeometry,
150 AraimError::InvalidIsm | AraimError::InvalidAllocation => SbasPlError::InvalidErrorModel,
151 AraimError::UnmonitorableFaultMass | AraimError::NumericalFailure => {
152 SbasPlError::NumericalFailure
153 }
154 }
155}
156
157fn map_araim_error(error: AraimError) -> SbasPlError {
158 match error {
159 AraimError::InsufficientGeometry => SbasPlError::InsufficientGeometry,
160 AraimError::InvalidIsm | AraimError::InvalidAllocation => SbasPlError::InvalidErrorModel,
161 AraimError::UnmonitorableFaultMass | AraimError::NumericalFailure => {
162 SbasPlError::NumericalFailure
163 }
164 }
165}
166
167fn map_integrity_error(error: IntegrityError) -> SbasPlError {
168 match error {
169 IntegrityError::Singular => SbasPlError::InsufficientGeometry,
170 IntegrityError::InvalidInput { .. }
171 | IntegrityError::NonFinite
172 | IntegrityError::NotPositiveSemidefinite
173 | IntegrityError::InvalidProbability { .. } => SbasPlError::NumericalFailure,
174 }
175}