gnss_qc/context/navigation/
mod.rs

1/*
2 * All Post Proecessed Navigation support & feature dependent stuff.
3 *
4 * Authors: Guillaume W. Bres <guillaume.bressaix@gmail.com> et al.
5 * (cf. https://github.com/rtk-rs/gnss-qc/graphs/contributors)
6 * This framework is shipped under Mozilla Public V2 license.
7 *
8 * Documentation:
9 * - https://github.com/rtk-rs/gnss-qc
10 * - https://github.com/rtk-rs/rinex
11 * - https://github.com/rtk-rs/sp3
12 */
13
14use thiserror::Error;
15
16use log::error;
17
18use anise::{
19    almanac::{
20        metaload::{MetaAlmanacError, MetaFile},
21        planetary::PlanetaryDataError,
22    },
23    constants::frames::{EARTH_ITRF93, EARTH_J2000},
24    errors::AlmanacError,
25    prelude::{Almanac, Frame, MetaAlmanac},
26};
27
28use crate::{
29    navigation::{NavFilter, NavFilterType},
30    prelude::{Constellation, QcContext},
31};
32
33#[cfg(feature = "navigation")]
34use crate::prelude::{Orbit, ReferenceEcefPosition};
35
36#[derive(Debug, Error)]
37pub enum NavigationError {
38    #[error("almanac error: {0}")]
39    Almanac(#[from] AlmanacError),
40    #[error("meta error: {0}")]
41    MetaAlmanac(#[from] MetaAlmanacError),
42    #[error("planetary data error")]
43    PlanetaryData(#[from] PlanetaryDataError),
44}
45
46impl QcContext {
47    fn anise_de440s_bsp() -> MetaFile {
48        MetaFile {
49            crc32: Some(0x7286750a),
50            uri: String::from("http://public-data.nyxspace.com/anise/de440s.bsp"),
51        }
52    }
53
54    fn anise_pck11_pca() -> MetaFile {
55        MetaFile {
56            crc32: Some(0x8213b6e9),
57            uri: String::from("http://public-data.nyxspace.com/anise/v0.5/pck11.pca"),
58        }
59    }
60
61    fn anise_jpl_bpc() -> MetaFile {
62        MetaFile {
63            crc32: None,
64            uri:
65                "https://naif.jpl.nasa.gov/pub/naif/generic_kernels/pck/earth_latest_high_prec.bpc"
66                    .to_string(),
67        }
68    }
69
70    /// This [MetaAlmanac] solely relies on the nyx-space servers
71    fn default_meta_almanac() -> MetaAlmanac {
72        MetaAlmanac {
73            files: vec![Self::anise_pck11_pca(), Self::anise_de440s_bsp()],
74        }
75    }
76
77    /// This [MetaAlmanac] solely relies on the nyx-space servers
78    fn high_precision_meta_almanac() -> MetaAlmanac {
79        MetaAlmanac {
80            files: vec![
81                Self::anise_pck11_pca(),
82                Self::anise_de440s_bsp(),
83                Self::anise_jpl_bpc(),
84            ],
85        }
86    }
87
88    /// Create a new [QcContext] using your own [Almanac] and [Frame] definitions
89    /// (obtained externally). NB: [Frame] is supposed to be one of the
90    /// Earth Centered Frame as we are supposed to operate on planet Earth.
91    /// This is typically used by advanced users targetting high precision naviation.
92    pub fn new_alamac_frame(almanac: Almanac, frame: Frame) -> Self {
93        Self {
94            files: Default::default(),
95            blob: Default::default(),
96            almanac,
97            earth_cef: frame,
98        }
99    }
100
101    /// Obtains [Almanac] + ECEF [Frame] definition from ANISE database
102    pub(crate) fn default_almanac_frame() -> (Almanac, Frame) {
103        let mut meta = Self::default_meta_almanac();
104
105        let almanac = match meta.process(false) {
106            Ok(almanac) => almanac,
107            Err(e) => {
108                error!("anise error: {}", e);
109                Almanac::default()
110            }
111        };
112
113        let frame = almanac
114            .frame_from_uid(EARTH_J2000)
115            .unwrap_or_else(|e| panic!("anise internal error: {}", e));
116
117        (almanac, frame)
118    }
119
120    /// Returns a possible [ReferenceEcefPosition] if defined in current [QcContext].
121    /// NB: this is only picked from a possible [Rinex] Observations, not any
122    /// other possible source. If no Observations were loaded, there is no point
123    /// asking for this in this current form.
124    pub fn reference_rx_position(&self) -> Option<ReferenceEcefPosition> {
125        let obs_rinex = self.observation()?;
126        let t = obs_rinex.first_epoch()?;
127        let rx_orbit = obs_rinex.header.rx_orbit(t, self.earth_cef)?;
128        let pos = ReferenceEcefPosition::from_orbit(&rx_orbit);
129        Some(pos)
130    }
131
132    /// Returns a possible reference position, expressed as [Orbit], if defined in current [QcContext].
133    /// NB: this is only picked from a possible [Rinex] Observations, not any
134    /// other possible source. If no Observations were loaded, there is no point
135    /// asking for this in this current form.
136    pub fn reference_rx_orbit(&self) -> Option<Orbit> {
137        let obs_rinex = self.observation()?;
138        let t = obs_rinex.first_epoch()?;
139        obs_rinex.header.rx_orbit(t, self.earth_cef)
140    }
141
142    /// Applies complex [NavFilter] to mutable [QcContext].
143    pub fn nav_filter_mut(&mut self, filter: &NavFilter) {
144        // apply nav conditions
145        if let Some(brdc) = self.brdc_navigation_mut() {
146            let any_constellation = filter.constellations.is_empty();
147            let broad_sbas = filter.constellations.contains(&Constellation::SBAS);
148
149            let brdc_rec = brdc.record.as_mut_nav().unwrap();
150
151            brdc_rec.retain(|k, data| {
152                if let Some(eph) = data.as_ephemeris() {
153                    match filter.filter {
154                        NavFilterType::Healthy => {
155                            if k.sv.constellation.is_sbas() && broad_sbas {
156                                eph.sv_healthy()
157                            } else {
158                                if any_constellation {
159                                    eph.sv_healthy()
160                                } else {
161                                    if filter.constellations.contains(&k.sv.constellation) {
162                                        eph.sv_healthy()
163                                    } else {
164                                        true
165                                    }
166                                }
167                            }
168                        }
169                        NavFilterType::Testing => {
170                            if k.sv.constellation.is_sbas() && broad_sbas {
171                                eph.sv_in_testing()
172                            } else {
173                                if any_constellation {
174                                    eph.sv_in_testing()
175                                } else {
176                                    if filter.constellations.contains(&k.sv.constellation) {
177                                        eph.sv_in_testing()
178                                    } else {
179                                        true
180                                    }
181                                }
182                            }
183                        }
184                        NavFilterType::Unhealthy => {
185                            if k.sv.constellation.is_sbas() && broad_sbas {
186                                !eph.sv_healthy()
187                            } else {
188                                if any_constellation {
189                                    !eph.sv_healthy()
190                                } else {
191                                    if filter.constellations.contains(&k.sv.constellation) {
192                                        !eph.sv_healthy()
193                                    } else {
194                                        true
195                                    }
196                                }
197                            }
198                        }
199                    }
200                } else {
201                    // preserves other frames
202                    true
203                }
204            });
205        }
206    }
207
208    /// Upgrade this [QcContext] for ultra high precision navigation.
209    pub fn with_jpl_bpc(&self) -> Result<(), NavigationError> {
210        let mut s = self.clone();
211
212        let mut meta = Self::high_precision_meta_almanac();
213        let almanac = meta.process(true)?;
214
215        s.almanac = almanac;
216
217        let mut meta = Self::default_meta_almanac();
218        let almanac = meta.process(true)?;
219
220        let frame = almanac.frame_from_uid(EARTH_ITRF93)?;
221        s.earth_cef = frame;
222
223        Ok(())
224    }
225}