gnss_qc/report/shared/
sampling.rs

1use maud::{html, Markup, Render};
2use rinex::prelude::{Duration, Epoch, Rinex};
3
4#[cfg(feature = "sp3")]
5use sp3::SP3;
6
7/// [SamplingReport] applies to all time domain products
8pub struct SamplingReport {
9    /// Total epochs
10    pub total: usize,
11    /// First [`Epoch`] identified in time
12    pub first_epoch: Epoch,
13    /// Last [`Epoch`] identified in time
14    pub last_epoch: Epoch,
15    /// Time span of this RINEX context
16    pub duration: Duration,
17    /// File [`Header`] sample rate
18    pub sampling_interval: Option<Duration>,
19    /// Dominant sample rate
20    pub dominant_sample_rate: Option<f64>,
21    /// Unusual data gaps
22    pub gaps: Vec<(Epoch, Duration)>,
23    /// longest gap detected
24    pub longest_gap: Option<(Epoch, Duration)>,
25    /// shortest gap detected
26    pub shortest_gap: Option<(Epoch, Duration)>,
27}
28
29impl SamplingReport {
30    pub fn from_rinex(rinex: &Rinex) -> Self {
31        let gaps = rinex.data_gaps(None).collect::<Vec<_>>();
32        Self {
33            total: rinex.epoch_iter().count(),
34            first_epoch: rinex
35                .first_epoch()
36                .expect("failed to determine first RINEX epoch, badly formed?"),
37            last_epoch: rinex
38                .last_epoch()
39                .expect("failed to determine last RINEX epoch, badly formed?"),
40            duration: rinex
41                .duration()
42                .expect("failed to determine RINEX time frame, badly formed?"),
43            sampling_interval: rinex.sampling_interval(),
44            dominant_sample_rate: rinex.dominant_sampling_rate_hz(),
45            shortest_gap: gaps
46                .iter()
47                .min_by(|(_, dur_a), (_, dur_b)| dur_a.partial_cmp(dur_b).unwrap())
48                .copied(),
49            longest_gap: gaps
50                .iter()
51                .max_by(|(_, dur_a), (_, dur_b)| dur_a.partial_cmp(dur_b).unwrap())
52                .copied(),
53            gaps,
54        }
55    }
56    #[cfg(feature = "sp3")]
57    pub fn from_sp3(sp3: &SP3) -> Self {
58        let t_start = sp3
59            .first_epoch()
60            .expect("undetermined first epoch: SP3 format error");
61
62        let t_end = sp3
63            .last_epoch()
64            .expect("undetermined last epoch: SP3 format error");
65        Self {
66            total: sp3.epochs_iter().count(),
67            gaps: Vec::new(),   // TODO
68            longest_gap: None,  //TODO
69            shortest_gap: None, //TODO
70            last_epoch: t_end,
71            first_epoch: t_start,
72            duration: t_end - t_start,
73            sampling_interval: Some(sp3.header.sampling_period),
74            dominant_sample_rate: Some(1.0 / sp3.header.sampling_period.to_seconds()),
75        }
76    }
77}
78
79impl Render for SamplingReport {
80    fn render(&self) -> Markup {
81        html! {
82            div class="table-container" {
83                table class="table is-bordered" {
84                    tbody {
85                        tr {
86                            th class="is-info" {
87                                "Time Frame"
88                            }
89                        }
90                        tr {
91                            th {
92                                "Start"
93                            }
94                            td {
95                                (self.first_epoch.to_string())
96                            }
97                            th {
98                                "End"
99                            }
100                            td {
101                                (self.last_epoch.to_string())
102                            }
103                        }
104                        tr {
105                            th {
106                                "Duration"
107                            }
108                            td {
109                                (self.duration.to_string())
110                            }
111                        }
112                        @if let Some(sampling_interval) = self.sampling_interval {
113                            tr {
114                                th class="is-info" {
115                                    "Sampling Period"
116                                }
117                            }
118                            tr {
119                                td {
120                                    (sampling_interval.to_string())
121                                }
122                            }
123                        }
124                        @if let Some(sample_rate) = self.dominant_sample_rate {
125                            tr {
126                                th class="is-info" {
127                                    "Dominant Sampling Rate"
128                                }
129                            }
130                            tr {
131                                td {
132                                    (format!("{:.3} Hz", sample_rate))
133                                }
134                            }
135                        }
136                        @if self.gaps.len() == 0 {
137                            tr {
138                                th class="is-primary" {
139                                    "Data gaps"
140                                }
141                                td {
142                                    "No gaps detected"
143                                }
144                            }
145                        } @else {
146                            tr {
147                                th class="is-warning" {
148                                    "Data gaps"
149                                }
150                                td {
151                                    (format!("{} Data gaps", self.gaps.len()))
152                                }
153                            }
154                            @if let Some((t_start, dur)) = self.shortest_gap {
155                                tr {
156                                    th {
157                                        "Shortest"
158                                    }
159                                    td {
160                                        (t_start.to_string())
161                                    }
162                                    td {
163                                        (dur.to_string())
164                                    }
165                                }
166                            }
167                            @if let Some((t_start, dur)) = self.longest_gap {
168                                tr {
169                                    th {
170                                        "Longest"
171                                    }
172                                    td {
173                                        (t_start.to_string())
174                                    }
175                                    td {
176                                        (dur.to_string())
177                                    }
178                                }
179                            }
180                        }
181                    }
182                }
183            }
184        }
185    }
186}