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 #[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 {
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 {
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}