rinex/meteo/
sensor.rs

1//! Meteo sensor
2use crate::prelude::{Observable, ParsingError};
3
4#[cfg(feature = "nav")]
5use anise::{
6    math::Vector6,
7    prelude::{Epoch, Frame, Orbit},
8};
9
10#[cfg(feature = "qc")]
11use maud::{html, Markup, Render};
12
13/// Meteo Observation Sensor
14#[derive(Default, Clone, Debug, PartialEq)]
15#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
16pub struct Sensor {
17    /// Physics measured by this sensor
18    pub observable: Observable,
19    /// Model of this sensor
20    pub model: Option<String>,
21    /// Type of sensor
22    pub sensor_type: Option<String>,
23    /// Sensor accuracy [°C,..]
24    pub accuracy: Option<f32>,
25    /// Posible sensor location (ECEF)
26    pub position: Option<(f64, f64, f64)>,
27    /// Possible sensor height eccentricity (m)
28    pub height: Option<f64>,
29}
30
31#[cfg(feature = "qc")]
32impl Render for Sensor {
33    fn render(&self) -> Markup {
34        html! {
35            table class="table is-bordered" {
36                tr {
37                    th { "Observable" }
38                    td { (self.observable.to_string()) }
39                }
40                @if let Some(model) = &self.model {
41                    tr {
42                        th { "Model" }
43                        td { (model) }
44                    }
45                }
46                @if let Some(sensor) = &self.sensor_type {
47                    tr {
48                        th { "Sensor Type" }
49                        td { (sensor) }
50                    }
51                }
52                @if let Some(accuracy) = self.accuracy {
53                    tr {
54                        th { "Sensor Accuracy" }
55                        td { (format!("{:.3E}", accuracy)) }
56                    }
57                }
58                @if cfg!(feature = "nav") {
59                    @if let Some((x_ecef_m, y_ecef_m, z_ecef_m)) = self.position {
60                        tr {
61                            th { "Sensor position (ECEF m)" }
62                            td {
63                                (format!("{:.3E}m", x_ecef_m))
64                            }
65                            td {
66                                (format!("{:.3E}m", y_ecef_m))
67                            }
68                            td {
69                                (format!("{:.3E}m", z_ecef_m))
70                            }
71                        }
72                    }
73                    @if let Some(h) = self.height {
74                        tr {
75                            th { "Height" }
76                            td { (format!("{:.3} m", h)) }
77                        }
78                    }
79                }
80            }
81        }
82    }
83}
84
85impl std::str::FromStr for Sensor {
86    type Err = ParsingError;
87    fn from_str(content: &str) -> Result<Self, Self::Err> {
88        let (model, rem) = content.split_at(20);
89        let (s_type, rem) = rem.split_at(20 + 6);
90        let (accuracy, rem) = rem.split_at(7 + 4);
91        let (observable, _) = rem.split_at(2);
92        Ok(Self {
93            model: {
94                if !model.trim().is_empty() {
95                    Some(model.trim().to_string())
96                } else {
97                    None
98                }
99            },
100            sensor_type: {
101                if !s_type.trim().is_empty() {
102                    Some(s_type.trim().to_string())
103                } else {
104                    None
105                }
106            },
107            accuracy: {
108                if let Ok(f) = f32::from_str(accuracy.trim()) {
109                    Some(f)
110                } else {
111                    None
112                }
113            },
114            height: None,
115            position: None,
116            observable: Observable::from_str(observable.trim())?,
117        })
118    }
119}
120
121impl std::fmt::Display for Sensor {
122    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
123        if let Some(model) = &self.model {
124            write!(f, "{:<width$}", model, width = 20)?
125        } else {
126            write!(f, "{:20}", "")?;
127        }
128
129        if let Some(stype) = &self.sensor_type {
130            write!(f, "{:<width$}", stype, width = 26)?;
131        } else {
132            write!(f, "{:26}", "")?;
133        }
134
135        if let Some(accuracy) = self.accuracy {
136            write!(f, "{:^11.1}", accuracy)?
137        } else {
138            write!(f, "{:11}", "")?
139        }
140        writeln!(f, "{} SENSOR MOD/TYPE/ACC", self.observable)?;
141
142        if let Some((x, y, z)) = self.position {
143            write!(f, "{:14.4}", x)?;
144            write!(f, "{:14.4}", y)?;
145            write!(f, "{:14.4}", z)?;
146
147            let h = self.height.unwrap_or(0.0);
148
149            write!(f, "{:14.4}", h)?;
150            writeln!(f, " {} SENSOR POS XYZ/H", self.observable)?
151        }
152        Ok(())
153    }
154}
155
156impl Sensor {
157    /// Define a new [Observable] sensor
158    pub fn new(observable: Observable) -> Self {
159        Self::default().with_observable(observable)
160    }
161
162    /// Copies and defines [Sensor] model
163    pub fn with_model(&self, model: &str) -> Self {
164        let mut s = self.clone();
165        s.model = Some(model.to_string());
166        s
167    }
168
169    /// Copies and defines [Sensor] type
170    pub fn with_type(&self, stype: &str) -> Self {
171        let mut s = self.clone();
172        s.sensor_type = Some(stype.to_string());
173        s
174    }
175
176    /// Copies and defines [Sensor] [Observable]
177    pub fn with_observable(&self, observable: Observable) -> Self {
178        let mut s = self.clone();
179        s.observable = observable;
180        s
181    }
182
183    /// Copies and define new sensor position
184    pub fn with_position(&self, position: (f64, f64, f64)) -> Self {
185        let mut s = self.clone();
186        s.position = Some(position);
187        s
188    }
189
190    /// Copies and define new sensor height eccentricity
191    pub fn with_height(&self, h: f64) -> Self {
192        let mut s = self.clone();
193        s.height = Some(h);
194        s
195    }
196
197    /// Copies and defines sensor accuracy
198    pub fn with_accuracy(&self, accuracy: f32) -> Self {
199        let mut s = self.clone();
200        s.accuracy = Some(accuracy);
201        s
202    }
203
204    #[cfg(feature = "nav")]
205    #[cfg_attr(docsrs, doc(cfg(feature = "nav")))]
206    /// Expresses [Sensor] position as an [Orbit]
207    pub fn rx_orbit(&self, t: Epoch, frame: Frame) -> Option<Orbit> {
208        let (x_ecef_m, y_ecef_m, z_ecef_m) = self.position?;
209
210        let pos_vel = Vector6::new(
211            x_ecef_m / 1000.0,
212            y_ecef_m / 1000.0,
213            z_ecef_m / 1000.0,
214            0.0,
215            0.0,
216            0.0,
217        );
218
219        Some(Orbit::from_cartesian_pos_vel(pos_vel, t, frame))
220    }
221}
222
223#[cfg(test)]
224// #[cfg(feature = "nav")]
225mod test {
226    use super::*;
227    // use anise::prelude::{Orbit, Frame};
228    use std::str::FromStr;
229
230    #[test]
231    fn test_formatting() {
232        let s = Sensor::new(Observable::Temperature);
233        assert_eq!(
234            s.to_string(),
235            "                                                         TD SENSOR MOD/TYPE/ACC\n"
236        );
237        let s = s.with_model("PAROSCIENTIFIC");
238        assert_eq!(
239            s.to_string(),
240            "PAROSCIENTIFIC                                           TD SENSOR MOD/TYPE/ACC\n"
241        );
242        let s = s.with_observable(Observable::Pressure);
243        let s = s.with_type("740-16B");
244        assert_eq!(
245            s.to_string(),
246            "PAROSCIENTIFIC      740-16B                              PR SENSOR MOD/TYPE/ACC\n"
247        );
248        let s = s.with_accuracy(0.2);
249        assert_eq!(
250            s.to_string(),
251            "PAROSCIENTIFIC      740-16B                       0.2    PR SENSOR MOD/TYPE/ACC\n"
252        );
253
254        // let rx_orbit = Orbit::from_position(
255        //     0.0,
256        //     0.0,
257        //     0.0,
258        //     Default::default(),
259        //     Frame::from_name("EARTH", ""),
260        // );
261
262        // let mut s = s.with_position(rx_orbit);
263
264        // s.height = Some(1234.5678);
265
266        // assert_eq!(
267        //     s.to_string(),
268        //     "PAROSCIENTIFIC      740-16B                       0.2    PR SENSOR MOD/TYPE/ACC
269        // 0.0000        0.0000        0.0000     1234.5678 PR SENSOR POS XYZ/H\n"
270        // );
271    }
272
273    #[test]
274    fn from_str() {
275        let s = Sensor::from_str("                                                  0.0    PR ");
276        assert!(s.is_ok());
277        let s = s.unwrap();
278        assert_eq!(s.model, None);
279        assert_eq!(s.sensor_type, None);
280        assert_eq!(s.accuracy, Some(0.0));
281        assert_eq!(s.observable, Observable::Pressure);
282
283        let s = Sensor::from_str(
284            "PAROSCIENTIFIC      740-16B                       0.2    PR SENSOR MOD/TYPE/ACC",
285        );
286        assert!(s.is_ok());
287
288        let s = Sensor::from_str(
289            "                                                  0.0    PR SENSOR MOD/TYPE/ACC",
290        );
291
292        assert!(s.is_ok());
293    }
294}