gnss_qc/report/orbital/
mixed.rs

1use rinex::prelude::{nav::Orbit, Constellation, Epoch, Rinex, SV};
2use std::collections::{BTreeMap, HashMap};
3
4use qc_traits::{Filter, Preprocessing};
5
6use crate::{
7    plot::{MapboxStyle, MarkerSymbol, Mode},
8    prelude::{html, Markup, Plot, QcContext, Render},
9};
10
11#[cfg(feature = "sp3")]
12use sp3::prelude::SP3;
13
14#[cfg(feature = "sp3")]
15struct BrdcSp3Report {
16    x_err_plot: Plot,
17    y_err_plot: Plot,
18    z_err_plot: Plot,
19}
20
21#[cfg(feature = "sp3")]
22impl BrdcSp3Report {
23    fn new(sp3: &SP3, brdc: &Rinex) -> Self {
24        let mut errors = BTreeMap::<SV, Vec<(Epoch, f64, f64, f64)>>::new();
25        for (t_sp3, sv_sp3, _, _, (sp3_x_km, sp3_y_km, sp3_z_km)) in
26            sp3.satellites_position_km_iter()
27        {
28            if let Some(brdc_orb) = brdc.sv_orbit(sv_sp3, t_sp3) {
29                let brdc_state = brdc_orb.to_cartesian_pos_vel();
30                let (nav_x_km, nav_y_km, nav_z_km) = (brdc_state[0], brdc_state[1], brdc_state[2]);
31
32                let (err_x_m, err_y_m, err_z_m) = (
33                    (nav_x_km - sp3_x_km) * 1000.0,
34                    (nav_y_km - sp3_y_km) * 1000.0,
35                    (nav_z_km - sp3_z_km) * 1000.0,
36                );
37
38                if let Some(errors) = errors.get_mut(&sv_sp3) {
39                    errors.push((t_sp3, err_x_m, err_y_m, err_z_m));
40                } else {
41                    errors.insert(sv_sp3, vec![(t_sp3, err_x_m, err_y_m, err_z_m)]);
42                }
43            }
44        }
45        Self {
46            x_err_plot: {
47                let mut plot = Plot::timedomain_plot(
48                    "sp3_brdc_x_err",
49                    "(BRDC - SP3) Position Errors",
50                    "Error [m]",
51                    true,
52                );
53                for (sv_index, (sv, errors)) in errors.iter().enumerate() {
54                    let error_t = errors.iter().map(|(t, _, _, _)| *t).collect::<Vec<_>>();
55                    let error_x = errors.iter().map(|(_, x, _, _)| *x).collect::<Vec<_>>();
56                    let trace = Plot::timedomain_chart(
57                        &sv.to_string(),
58                        Mode::Markers,
59                        MarkerSymbol::Diamond,
60                        &error_t,
61                        error_x,
62                        sv_index < 4,
63                    );
64                    plot.add_trace(trace);
65                }
66                plot
67            },
68            y_err_plot: {
69                let mut plot = Plot::timedomain_plot(
70                    "sp3_brdc_y_err",
71                    "(BRDC - SP3) Position Errors",
72                    "Error [m]",
73                    true,
74                );
75                for (sv_index, (sv, errors)) in errors.iter().enumerate() {
76                    let error_t = errors.iter().map(|(t, _, _, _)| *t).collect::<Vec<_>>();
77                    let error_y = errors.iter().map(|(_, _, y, _)| *y).collect::<Vec<_>>();
78                    let trace = Plot::timedomain_chart(
79                        &sv.to_string(),
80                        Mode::Markers,
81                        MarkerSymbol::Diamond,
82                        &error_t,
83                        error_y,
84                        sv_index < 4,
85                    );
86                    plot.add_trace(trace);
87                }
88                plot
89            },
90            z_err_plot: {
91                let mut plot = Plot::timedomain_plot(
92                    "sp3_brdc_z_err",
93                    "(BRDC - SP3) Position Errors",
94                    "Error [m]",
95                    true,
96                );
97                for (sv_index, (sv, errors)) in errors.iter().enumerate() {
98                    let error_t = errors.iter().map(|(t, _, _, _)| *t).collect::<Vec<_>>();
99                    let error_z = errors.iter().map(|(_, _, _, z)| *z).collect::<Vec<_>>();
100                    let trace = Plot::timedomain_chart(
101                        &sv.to_string(),
102                        Mode::Markers,
103                        MarkerSymbol::Diamond,
104                        &error_t,
105                        error_z,
106                        sv_index < 4,
107                    );
108                    plot.add_trace(trace);
109                }
110                plot
111            },
112        }
113    }
114}
115
116#[cfg(feature = "sp3")]
117impl Render for BrdcSp3Report {
118    fn render(&self) -> Markup {
119        html! {
120            div class="table-container" {
121                table class="table is-bordered" {
122                    tr {
123                        th class="is-info" {
124                            "X errors"
125                        }
126                        td {
127                            (self.x_err_plot.render())
128                        }
129                    }
130                    tr {
131                        th class="is-info" {
132                            "Y errors"
133                        }
134                        td {
135                            (self.y_err_plot.render())
136                        }
137                    }
138                    tr {
139                        th class="is-info" {
140                            "Z errors"
141                        }
142                        td {
143                            (self.z_err_plot.render())
144                        }
145                    }
146                }
147            }
148        }
149    }
150}
151
152pub struct OrbitReport {
153    sky_plot: Plot,
154    elev_plot: Plot,
155    map_proj: Plot,
156    // globe_proj: Plot,
157    #[cfg(feature = "sp3")]
158    brdc_sp3_err: HashMap<Constellation, BrdcSp3Report>,
159}
160
161impl OrbitReport {
162    pub fn new(ctx: &QcContext, reference: Option<Orbit>) -> Self {
163        let mut t_sp3 = BTreeMap::<SV, Vec<Epoch>>::new();
164        let mut elev_sp3 = BTreeMap::<SV, Vec<f64>>::new();
165        let mut azim_sp3 = BTreeMap::<SV, Vec<f64>>::new();
166        let mut sp3_lat_ddeg = BTreeMap::<SV, Vec<f64>>::new();
167        let mut sp3_long_ddeg = BTreeMap::<SV, Vec<f64>>::new();
168
169        #[cfg(feature = "sp3")]
170        if let Some(sp3) = ctx.sp3() {
171            if let Some(rx_orbit) = reference {
172                for (t, sp3_sv, sp3_orbit) in sp3.satellites_orbit_iter(ctx.earth_cef) {
173                    if let Ok(az_el_range) = ctx
174                        .almanac
175                        .azimuth_elevation_range_sez(sp3_orbit, rx_orbit, None, None)
176                    {
177                        let (lat_ddeg, long_ddeg, _) = sp3_orbit
178                            .latlongalt()
179                            .unwrap_or_else(|e| panic!("laglongalt: physical error: {}", e));
180
181                        if let Some(t_sp3) = t_sp3.get_mut(&sp3_sv) {
182                            t_sp3.push(t);
183                        } else {
184                            t_sp3.insert(sp3_sv, vec![t]);
185                        }
186
187                        if let Some(e) = elev_sp3.get_mut(&sp3_sv) {
188                            e.push(az_el_range.elevation_deg);
189                        } else {
190                            elev_sp3.insert(sp3_sv, vec![az_el_range.elevation_deg]);
191                        }
192
193                        if let Some(a) = azim_sp3.get_mut(&sp3_sv) {
194                            a.push(az_el_range.azimuth_deg);
195                        } else {
196                            azim_sp3.insert(sp3_sv, vec![az_el_range.azimuth_deg]);
197                        }
198
199                        if let Some(lat) = sp3_lat_ddeg.get_mut(&sp3_sv) {
200                            lat.push(lat_ddeg);
201                        } else {
202                            sp3_lat_ddeg.insert(sp3_sv, vec![lat_ddeg]);
203                        }
204
205                        if let Some(lon) = sp3_long_ddeg.get_mut(&sp3_sv) {
206                            lon.push(long_ddeg);
207                        } else {
208                            sp3_long_ddeg.insert(sp3_sv, vec![long_ddeg]);
209                        }
210                    }
211                }
212            }
213        }
214
215        Self {
216            sky_plot: {
217                let mut plot = Plot::sky_plot("skyplot", "Sky plot", true);
218                for (sv_index, (sv, epochs)) in t_sp3.iter().enumerate() {
219                    let visible = sv_index < 4;
220                    let elev_sp3 = elev_sp3.get(&sv).unwrap();
221                    let azim_sp3 = azim_sp3.get(&sv).unwrap();
222                    let trace = Plot::sky_trace(
223                        &sv.to_string(),
224                        epochs,
225                        elev_sp3.to_vec(),
226                        azim_sp3.to_vec(),
227                        visible,
228                    );
229                    plot.add_trace(trace);
230                }
231                plot
232            },
233            elev_plot: {
234                let mut elev_plot =
235                    Plot::timedomain_plot("elev_plot", "Elevation", "Elevation [deg°]", true);
236                for (sv_index, (sv, epochs)) in t_sp3.iter().enumerate() {
237                    let elev = elev_sp3.get(&sv).unwrap();
238                    let trace = Plot::timedomain_chart(
239                        &sv.to_string(),
240                        Mode::Markers,
241                        MarkerSymbol::Diamond,
242                        epochs,
243                        elev.to_vec(),
244                        sv_index < 4,
245                    );
246                    elev_plot.add_trace(trace);
247                }
248                elev_plot
249            },
250            map_proj: {
251                let mut map_proj = Plot::world_map(
252                    "map_proj",
253                    "Map Projection",
254                    MapboxStyle::OpenStreetMap,
255                    (32.0, -40.0),
256                    1,
257                    true,
258                );
259
260                #[cfg(feature = "sp3")]
261                if let Some(sp3) = ctx.sp3() {
262                    for (sv_index, sv) in sp3.satellites_iter().enumerate() {
263                        let lat_ddeg = sp3_lat_ddeg
264                            .iter()
265                            .filter_map(
266                                |(svnn, v)| if *svnn == sv { Some(v.clone()) } else { None },
267                            )
268                            .collect::<Vec<_>>();
269
270                        let long_ddeg = sp3_long_ddeg
271                            .iter()
272                            .filter_map(
273                                |(svnn, v)| if *svnn == sv { Some(v.clone()) } else { None },
274                            )
275                            .collect::<Vec<_>>();
276
277                        let map = Plot::mapbox(
278                            lat_ddeg,
279                            long_ddeg,
280                            &sv.to_string(),
281                            5,
282                            MarkerSymbol::Circle,
283                            None,
284                            1.0,
285                            sv_index < 2,
286                        );
287
288                        map_proj.add_trace(map);
289                    }
290                }
291                map_proj
292            },
293            #[cfg(feature = "sp3")]
294            brdc_sp3_err: {
295                let mut reports = HashMap::<Constellation, BrdcSp3Report>::new();
296                if let Some(sp3) = ctx.sp3() {
297                    if let Some(nav) = ctx.brdc_navigation() {
298                        for constellation in sp3.constellations_iter() {
299                            if let Some(constellation) = nav
300                                .constellations_iter()
301                                .filter(|c| *c == constellation)
302                                .reduce(|k, _| k)
303                            {
304                                let filter = Filter::equals(&constellation.to_string()).unwrap();
305                                let focused_sp3 = sp3.filter(&filter);
306                                let focused_nav = nav.filter(&filter);
307                                reports.insert(
308                                    constellation,
309                                    BrdcSp3Report::new(&focused_sp3, &focused_nav),
310                                );
311                            }
312                        }
313                    }
314                }
315                reports
316            },
317        }
318    }
319    pub fn html_inline_menu_bar(&self) -> Markup {
320        html! {
321            a id="menu:orbit" {
322                span class="icon" {
323                    i class="fa-solid fa-globe" {}
324                }
325                "Orbital projections"
326            }
327        }
328    }
329}
330
331#[cfg(feature = "sp3")]
332impl Render for OrbitReport {
333    fn render(&self) -> Markup {
334        html! {
335            div class="table-container" {
336                table class="table is-bordered" {
337                    tr {
338                        th class="is-info" {
339                            "Map projection"
340                        }
341                        td {
342                            (self.map_proj.render())
343                        }
344                    }
345                    //tr {
346                    //    th class="is-info" {
347                    //        "Globe projection"
348                    //    }
349                    //    td {
350                    //        (self.globe_proj.render())
351                    //    }
352                    //}
353                    tr {
354                        th class="is-info" {
355                            "Sky plot"
356                        }
357                        td {
358                            (self.sky_plot.render())
359                        }
360                    }
361                    tr {
362                        th class="is-info" {
363                            "Elevation"
364                        }
365                        td {
366                            (self.elev_plot.render())
367                        }
368                    }
369                    @if self.brdc_sp3_err.len() > 0 {
370                        @for (constell, page) in self.brdc_sp3_err.iter() {
371                            tr {
372                                th class="is-info" {
373                                    (format!("{} SP3/BRDC", constell))
374                                }
375                                td {
376                                    (page.render())
377                                }
378                            }
379                        }
380                    }
381                }
382            }
383        }
384    }
385}
386
387#[cfg(not(feature = "sp3"))]
388impl Render for OrbitReport {
389    fn render(&self) -> Markup {
390        html! {
391            div class="table-container" {
392                table class="table is-bordered" {
393                    tr {
394                        th class="is-info" {
395                            "Map projection"
396                        }
397                        td {
398                            (self.map_proj.render())
399                        }
400                    }
401                    //tr {
402                    //    th class="is-info" {
403                    //        "Globe projection"
404                    //    }
405                    //    td {
406                    //        (self.globe_proj.render())
407                    //    }
408                    //}
409                    tr {
410                        th class="is-info" {
411                            "Sky plot"
412                        }
413                        td {
414                            (self.sky_plot.render())
415                        }
416                    }
417                    tr {
418                        th class="is-info" {
419                            "Elevation"
420                        }
421                        td {
422                            (self.elev_plot.render())
423                        }
424                    }
425                }
426            }
427        }
428    }
429}