1use std::collections::{BTreeMap, BTreeSet};
4use std::fmt;
5
6pub use crate::dop::{dop, Dop, DopError, LineOfSight};
7use crate::ephemeris::{BroadcastEphemeris, Sp3};
8pub use crate::quality::{
9 spp_robust_fde_driver, FdeError, FdeOptions, FdeResult, FdeSppError, FdeSppOptions,
10};
11use crate::rinex::observations::{pseudoranges, ObsEpochTime, ObservationFile, SignalPolicy};
12pub use crate::spp::{
13 residual_rms, solve, solve_broadcast, solve_doppler_velocity, solve_spp_batch_parallel,
14 solve_spp_batch_serial, solve_with_doppler_velocity, solve_with_fallback, solve_with_policy,
15 solve_with_solver, BroadcastReason, Corrections, DopplerObservation, DopplerVelocityInputs,
16 EphemerisSource, FallbackError, FixSource, GalileoNequickCoeffs, KlobucharCoeffs, Observation,
17 ReceiverSolution, RejectedSat, RejectionReason, RobustConfig, SolutionMetadata, SolveInputs,
18 SolvePolicy, SolvePolicyError, SourcedSolution, SppDopplerSolution, SppError, SurfaceMet,
19 DEFAULT_HUBER_K, DEFAULT_ROBUST_MAX_OUTER, DEFAULT_ROBUST_OUTER_TOL_M,
20 DEFAULT_ROBUST_SCALE_FLOOR_M, ELEVATION_MASK_RAD, SIGMA0_M, TRANSMIT_TIME_ITERATIONS,
21};
22pub use crate::static_positioning::{
23 solve_static, StaticClockBias, StaticCovariance, StaticEpoch, StaticEpochInfluence,
24 StaticInfluenceStatus, StaticResidual, StaticSatelliteBatchInfluence, StaticSatelliteInfluence,
25 StaticSolution, StaticSolutionMetadata, StaticSolveError, StaticSolveOptions,
26};
27pub use crate::static_reference_station::{
28 solve_static_reference_station_rinex, StaticReferenceCarrierRinexOptions,
29 StaticReferenceCarrierSolution, StaticReferenceCodeSolution, StaticReferenceEpochDiagnostic,
30 StaticReferenceFixStatus, StaticReferenceModeError, StaticReferenceModeReport,
31 StaticReferenceModeStatus, StaticReferenceStationCovariance, StaticReferenceStationError,
32 StaticReferenceStationMode, StaticReferenceStationRinexOptions, StaticReferenceStationSolution,
33};
34use crate::{astro::time, Error as CoreError, GnssSatelliteId};
35
36pub type Solution = ReceiverSolution;
38
39pub type Error = SppError;
41
42#[derive(Debug, Clone, PartialEq, Eq)]
45#[non_exhaustive]
46pub enum RinexSppError {
47 Observation(CoreError),
49 MissingApproxPosition,
52}
53
54impl fmt::Display for RinexSppError {
55 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56 match self {
57 Self::Observation(error) => write!(f, "RINEX SPP observation assembly failed: {error}"),
58 Self::MissingApproxPosition => {
59 f.write_str("RINEX SPP assembly needs APPROX POSITION XYZ or an initial guess")
60 }
61 }
62 }
63}
64
65impl std::error::Error for RinexSppError {}
66
67impl From<CoreError> for RinexSppError {
68 fn from(error: CoreError) -> Self {
69 Self::Observation(error)
70 }
71}
72
73#[derive(Debug, Clone, PartialEq)]
82pub struct RinexSppBroadcastCorrections {
83 pub klobuchar: KlobucharCoeffs,
86 pub beidou_klobuchar: Option<KlobucharCoeffs>,
89 pub galileo_nequick: Option<GalileoNequickCoeffs>,
91 pub glonass_channels: BTreeMap<u8, i8>,
93}
94
95impl Default for RinexSppBroadcastCorrections {
96 fn default() -> Self {
97 Self {
98 klobuchar: zero_klobuchar(),
99 beidou_klobuchar: None,
100 galileo_nequick: None,
101 glonass_channels: BTreeMap::new(),
102 }
103 }
104}
105
106pub trait RinexSppAssemblySource {
113 fn rinex_spp_broadcast_corrections(&self) -> RinexSppBroadcastCorrections;
115}
116
117impl RinexSppAssemblySource for BroadcastEphemeris {
118 fn rinex_spp_broadcast_corrections(&self) -> RinexSppBroadcastCorrections {
119 let iono = self.iono_corrections();
120 let gps = iono.gps.map(klobuchar_from_alpha_beta);
121 RinexSppBroadcastCorrections {
122 klobuchar: gps.unwrap_or_else(zero_klobuchar),
123 beidou_klobuchar: iono.beidou.map(klobuchar_from_alpha_beta),
124 galileo_nequick: iono.galileo,
125 glonass_channels: self.glonass_frequency_channels(),
126 }
127 }
128}
129
130impl RinexSppAssemblySource for Sp3 {
131 fn rinex_spp_broadcast_corrections(&self) -> RinexSppBroadcastCorrections {
132 RinexSppBroadcastCorrections::default()
133 }
134}
135
136pub struct RinexSppSource<'a, E: EphemerisSource + ?Sized> {
143 ephemeris: &'a E,
144 broadcast: Option<&'a BroadcastEphemeris>,
145}
146
147impl<'a, E: EphemerisSource + ?Sized> RinexSppSource<'a, E> {
148 #[must_use]
150 pub const fn new(ephemeris: &'a E) -> Self {
151 Self {
152 ephemeris,
153 broadcast: None,
154 }
155 }
156
157 #[must_use]
160 pub const fn with_broadcast_context(
161 ephemeris: &'a E,
162 broadcast: &'a BroadcastEphemeris,
163 ) -> Self {
164 Self {
165 ephemeris,
166 broadcast: Some(broadcast),
167 }
168 }
169
170 #[must_use]
172 pub const fn ephemeris(&self) -> &'a E {
173 self.ephemeris
174 }
175
176 #[must_use]
178 pub const fn broadcast_context(&self) -> Option<&'a BroadcastEphemeris> {
179 self.broadcast
180 }
181}
182
183impl<E: EphemerisSource + ?Sized> EphemerisSource for RinexSppSource<'_, E> {
184 fn position_clock_at_j2000_s(
185 &self,
186 sat: GnssSatelliteId,
187 t_j2000_s: f64,
188 ) -> Option<([f64; 3], f64)> {
189 self.ephemeris.position_clock_at_j2000_s(sat, t_j2000_s)
190 }
191}
192
193impl<E: EphemerisSource + ?Sized> RinexSppAssemblySource for RinexSppSource<'_, E> {
194 fn rinex_spp_broadcast_corrections(&self) -> RinexSppBroadcastCorrections {
195 self.broadcast
196 .map(RinexSppAssemblySource::rinex_spp_broadcast_corrections)
197 .unwrap_or_default()
198 }
199}
200
201#[derive(Debug, Clone, PartialEq)]
203pub struct RinexSppOptions {
204 pub signal_policy: SignalPolicy,
206 pub corrections: Corrections,
208 pub initial_guess: Option<[f64; 4]>,
212 pub satellites: Option<BTreeSet<GnssSatelliteId>>,
215 pub met: SurfaceMet,
217 pub robust: Option<RobustConfig>,
219}
220
221impl RinexSppOptions {
222 #[must_use]
224 pub fn new(signal_policy: SignalPolicy) -> Self {
225 Self {
226 signal_policy,
227 corrections: Corrections::IONO_TROPO,
228 initial_guess: None,
229 satellites: None,
230 met: SurfaceMet::default(),
231 robust: None,
232 }
233 }
234
235 pub fn default_for(obs: &ObservationFile) -> Result<Self, RinexSppError> {
238 Ok(Self::new(SignalPolicy::default_for(obs.header().version)?))
239 }
240
241 #[must_use]
243 pub const fn with_corrections(mut self, corrections: Corrections) -> Self {
244 self.corrections = corrections;
245 self
246 }
247
248 #[must_use]
250 pub const fn with_initial_guess(mut self, initial_guess: [f64; 4]) -> Self {
251 self.initial_guess = Some(initial_guess);
252 self
253 }
254
255 #[must_use]
257 pub fn with_satellites<I>(mut self, satellites: I) -> Self
258 where
259 I: IntoIterator<Item = GnssSatelliteId>,
260 {
261 self.satellites = Some(satellites.into_iter().collect());
262 self
263 }
264
265 #[must_use]
267 pub const fn with_surface_met(mut self, met: SurfaceMet) -> Self {
268 self.met = met;
269 self
270 }
271
272 #[must_use]
274 pub const fn with_robust(mut self, robust: Option<RobustConfig>) -> Self {
275 self.robust = robust;
276 self
277 }
278}
279
280#[derive(Debug, Clone)]
282pub struct RinexSppEpochInputs {
283 pub epoch_index: usize,
285 pub epoch: ObsEpochTime,
287 pub inputs: SolveInputs,
289}
290
291#[derive(Debug, Clone)]
293pub struct RinexSppEpochSolution {
294 pub epoch_index: usize,
296 pub epoch: ObsEpochTime,
298 pub solution: Result<ReceiverSolution, SolvePolicyError>,
300}
301
302pub fn spp_inputs_from_rinex_obs<S>(
313 obs: &ObservationFile,
314 source: &S,
315 options: &RinexSppOptions,
316) -> Result<Vec<RinexSppEpochInputs>, RinexSppError>
317where
318 S: RinexSppAssemblySource + ?Sized,
319{
320 let initial_guess = initial_guess(obs, options)?;
321 let base_corrections = merged_broadcast_corrections(obs, source);
322 let mut out = Vec::new();
323
324 for (epoch_index, epoch) in obs.epochs().iter().enumerate() {
325 if epoch.flag > 1 {
326 continue;
327 }
328 let mut selected = pseudoranges(obs, epoch, &options.signal_policy)?;
329 if let Some(allowed) = &options.satellites {
330 selected.retain(|(sat, _)| allowed.contains(sat));
331 }
332 if selected.is_empty() {
333 continue;
334 }
335
336 let epoch_context = epoch_time_context(epoch.epoch);
337 let observations = selected
338 .into_iter()
339 .map(|(satellite_id, pseudorange_m)| Observation {
340 satellite_id,
341 pseudorange_m,
342 })
343 .collect();
344
345 out.push(RinexSppEpochInputs {
346 epoch_index,
347 epoch: epoch.epoch,
348 inputs: SolveInputs {
349 observations,
350 t_rx_j2000_s: epoch_context.t_rx_j2000_s,
351 t_rx_second_of_day_s: epoch_context.t_rx_second_of_day_s,
352 day_of_year: epoch_context.day_of_year,
353 initial_guess,
354 corrections: options.corrections,
355 klobuchar: base_corrections.klobuchar,
356 beidou_klobuchar: base_corrections.beidou_klobuchar,
357 galileo_nequick: base_corrections.galileo_nequick,
358 sbas_iono: None,
359 glonass_channels: base_corrections.glonass_channels.clone(),
360 met: options.met,
361 robust: options.robust,
362 },
363 });
364 }
365
366 Ok(out)
367}
368
369pub fn solve_spp_from_rinex_obs<S>(
375 source: &S,
376 obs: &ObservationFile,
377 options: &RinexSppOptions,
378 with_geodetic: bool,
379 policy: SolvePolicy,
380) -> Result<Vec<RinexSppEpochSolution>, RinexSppError>
381where
382 S: EphemerisSource + RinexSppAssemblySource,
383{
384 let epochs = spp_inputs_from_rinex_obs(obs, source, options)?;
385 let inputs = epochs
386 .iter()
387 .map(|epoch| epoch.inputs.clone())
388 .collect::<Vec<_>>();
389 let results = solve_spp_batch_serial(source, &inputs, with_geodetic, policy);
390 Ok(epochs
391 .into_iter()
392 .zip(results)
393 .map(|(epoch, solution)| RinexSppEpochSolution {
394 epoch_index: epoch.epoch_index,
395 epoch: epoch.epoch,
396 solution,
397 })
398 .collect())
399}
400
401fn klobuchar_from_alpha_beta(value: crate::ephemeris::KlobucharAlphaBeta) -> KlobucharCoeffs {
402 KlobucharCoeffs {
403 alpha: value.alpha,
404 beta: value.beta,
405 }
406}
407
408const fn zero_klobuchar() -> KlobucharCoeffs {
409 KlobucharCoeffs {
410 alpha: [0.0; 4],
411 beta: [0.0; 4],
412 }
413}
414
415fn initial_guess(
416 obs: &ObservationFile,
417 options: &RinexSppOptions,
418) -> Result<[f64; 4], RinexSppError> {
419 if let Some(initial_guess) = options.initial_guess {
420 return Ok(initial_guess);
421 }
422 let approx = obs
423 .header()
424 .approx_position_m
425 .ok_or(RinexSppError::MissingApproxPosition)?;
426 Ok([approx[0], approx[1], approx[2], 0.0])
427}
428
429fn merged_broadcast_corrections<S>(
430 obs: &ObservationFile,
431 source: &S,
432) -> RinexSppBroadcastCorrections
433where
434 S: RinexSppAssemblySource + ?Sized,
435{
436 let mut corrections = source.rinex_spp_broadcast_corrections();
437 corrections.glonass_channels.extend(
438 obs.header()
439 .glonass_slots
440 .iter()
441 .map(|(&slot, &channel)| (slot, channel)),
442 );
443 corrections
444}
445
446struct EpochTimeContext {
447 t_rx_j2000_s: f64,
448 t_rx_second_of_day_s: f64,
449 day_of_year: f64,
450}
451
452fn epoch_time_context(epoch: ObsEpochTime) -> EpochTimeContext {
453 let year = epoch.year;
454 let month = i32::from(epoch.month);
455 let day = i32::from(epoch.day);
456 let hour = i32::from(epoch.hour);
457 let minute = i32::from(epoch.minute);
458 EpochTimeContext {
459 t_rx_j2000_s: time::j2000_seconds(year, month, day, hour, minute, epoch.second),
460 t_rx_second_of_day_s: time::second_of_day(hour, minute, epoch.second),
461 day_of_year: time::day_of_year(year, month, day, hour, minute, epoch.second),
462 }
463}