gnss_qc/report/rinex/
ionex.rs

1use crate::report::Error;
2use maud::{html, Markup, Render};
3use rinex::ionex::{MappingFunction, RefSystem as Reference};
4use rinex::prelude::{Duration, Epoch, Rinex};
5
6use crate::plot::{MapboxStyle, Plot, Visible};
7
8use plotly::{
9    layout::update_menu::{Button, ButtonBuilder},
10    DensityMapbox,
11};
12
13pub struct IonexReport {
14    nb_of_maps: usize,
15    map_dimension: u8,
16    epoch_first_map: Epoch,
17    epoch_last_map: Epoch,
18    sampling_interval: Option<Duration>,
19    reference: Reference,
20    description: Option<String>,
21    mapping: Option<MappingFunction>,
22    world_map: Plot,
23}
24
25impl IonexReport {
26    pub fn new(rnx: &Rinex) -> Result<Self, Error> {
27        let nb_of_maps = rnx.epoch_iter().count();
28        let header = rnx.header.ionex.as_ref().ok_or(Error::MissingIonexHeader)?;
29        Ok(Self {
30            nb_of_maps,
31            map_dimension: header.map_dimension,
32            epoch_last_map: header.epoch_of_last_map,
33            epoch_first_map: header.epoch_of_first_map,
34            mapping: header.mapping.clone(),
35            reference: header.reference.clone(),
36            description: header.description.clone(),
37            sampling_interval: rnx.sampling_interval(),
38            world_map: {
39                let mut plot = Plot::world_map(
40                    "ionex_tec",
41                    "Ionosphere TEC maps",
42                    MapboxStyle::OpenStreetMap,
43                    (32.5, -40.0),
44                    0,
45                    true,
46                );
47
48                // Build one trace (1 map) per Epoch
49                let mut buttons = Vec::<Button>::new();
50
51                for (epoch_index, epoch) in rnx.epoch_iter().enumerate() {
52                    let label = epoch.to_string();
53
54                    let lat = rnx
55                        .ionex_tecu_latlong_ddeg_alt_km_iter()
56                        .filter_map(
57                            |(t, _, lat, _, _)| {
58                                if t == epoch {
59                                    Some(lat)
60                                } else {
61                                    None
62                                }
63                            },
64                        )
65                        .collect::<Vec<_>>();
66
67                    let long = rnx
68                        .ionex_tecu_latlong_ddeg_alt_km_iter()
69                        .filter_map(
70                            |(t, _, long, _, _)| {
71                                if t == epoch {
72                                    Some(long)
73                                } else {
74                                    None
75                                }
76                            },
77                        )
78                        .collect::<Vec<_>>();
79
80                    let tecu = rnx
81                        .ionex_tecu_latlong_ddeg_alt_km_iter()
82                        .filter_map(
83                            |(t, tecu, _, _, _)| {
84                                if t == epoch {
85                                    Some(tecu)
86                                } else {
87                                    None
88                                }
89                            },
90                        )
91                        .collect::<Vec<_>>();
92
93                    let trace = Plot::density_mapbox(
94                        lat.clone(),
95                        long.clone(),
96                        tecu,
97                        &label,
98                        0.6,
99                        3,
100                        epoch_index == 0,
101                    );
102
103                    plot.add_trace(trace);
104
105                    buttons.push(
106                        ButtonBuilder::new()
107                            .name("Epoch")
108                            .label(&label)
109                            .push_restyle(DensityMapbox::<f64, f64, f64>::modify_visible(
110                                (0..nb_of_maps)
111                                    .map(|i| {
112                                        if epoch_index == i {
113                                            Visible::True
114                                        } else {
115                                            Visible::False
116                                        }
117                                    })
118                                    .collect(),
119                            ))
120                            .build(),
121                    );
122                }
123
124                plot.add_custom_controls(buttons);
125                plot
126            },
127        })
128    }
129    pub fn html_inline_menu_bar(&self) -> Markup {
130        html! {
131            a id="menu:ionex" {
132                span class="icon" {
133                    i class="fa-solid fa-earth-americas" {}
134                }
135                "Ionosphere Maps (IONEX)"
136            }
137        }
138    }
139}
140
141impl Render for IonexReport {
142    fn render(&self) -> Markup {
143        html! {
144            table class="table is-bordered" {
145                tr {
146                    @if self.map_dimension == 2 {
147                        th class="is-info"{
148                            button aria-label="Isosurface TEC maps" data-balloon-pos="right" {
149                                "2D IONEX"
150                            }
151                        }
152                    } @else {
153                        th class="is-info" {
154                            button aria-label="Isofurface TEC maps by altitude layers" data-balloon-pos="right" {
155                                "3D IONEX"
156                            }
157                        }
158                    }
159                }
160                tr {
161                    th class="is-info" {
162                        "Number of Maps"
163                    }
164                    td {
165                        (self.nb_of_maps)
166                    }
167                }
168                tr {
169                    th class="is-info" {
170                        "Sampling Period"
171                    }
172                    @ if let Some(sampling_interval) = &self.sampling_interval {
173                        td {
174                            (sampling_interval.to_string())
175                        }
176                    } @ else {
177                        td class="is-warning" {
178                            "Unknown"
179                        }
180                    }
181                }
182                tr {
183                    th class="is-info" {
184                        "Epoch of first map"
185                    }
186                    td {
187                        (self.epoch_first_map.to_string())
188                    }
189                }
190                tr {
191                    th class="is-info" {
192                        "Epoch of Last map"
193                    }
194                    td {
195                        (self.epoch_last_map.to_string())
196                    }
197                }
198                tr {
199                    th class="is-info" {
200                        "Reference"
201                    }
202                    td {
203                        (self.reference.to_string())
204                    }
205                }
206                @if let Some(desc) = &self.description {
207                    tr {
208                        th class="is-info" {
209                            "Description"
210                        }
211                        td {
212                            (desc)
213                        }
214                    }
215                }
216                @if let Some(mapf) = &self.mapping {
217                    tr {
218                        th class="is-info" {
219                            button aria-label="Mapping function used in TEC map evaluation" data-balloon-pos="right" {
220                                "Mapping function"
221                            }
222                        }
223                        td {
224                            (mapf.to_string())
225                        }
226                    }
227                }
228                tr {
229                    th class="is-info" {
230                        "TEC Map"
231                    }
232                }
233                tr {
234                    td {
235                        (self.world_map.render())
236                    }
237                }
238            }
239        }
240    }
241}