Skip to main content

nyx_space/od/msr/
measurement.rs

1/*
2    Nyx, blazing fast astrodynamics
3    Copyright (C) 2018-onwards Christopher Rabotin <christopher.rabotin@gmail.com>
4
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU Affero General Public License as published
7    by the Free Software Foundation, either version 3 of the License, or
8    (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU Affero General Public License for more details.
14
15    You should have received a copy of the GNU Affero General Public License
16    along with this program.  If not, see <https://www.gnu.org/licenses/>.
17*/
18
19use super::MeasurementType;
20use hifitime::Epoch;
21use indexmap::{IndexMap, IndexSet};
22use nalgebra::{allocator::Allocator, DefaultAllocator, DimName, OVector};
23use std::fmt;
24
25/// A type-agnostic simultaneous measurement storage structure. Allows storing any number of simultaneous measurement of a given taker.
26///
27/// Note that two measurements are considered equal if the tracker and epoch match exactly, and if both have the same measurement types,
28/// and those measurements are equal to within 1e-10 (this allows for some leeway in TDM producers).
29#[derive(Clone, Debug)]
30pub struct Measurement {
31    /// Tracker alias which made this measurement
32    pub tracker: String,
33    /// Epoch of the measurement
34    pub epoch: Epoch,
35    /// All measurements made simultaneously
36    pub data: IndexMap<MeasurementType, f64>,
37    /// Whether this measurement has been manually rejected
38    pub rejected: bool,
39}
40
41impl Measurement {
42    pub fn new(tracker: String, epoch: Epoch) -> Self {
43        Self {
44            tracker,
45            epoch,
46            data: IndexMap::new(),
47            rejected: false,
48        }
49    }
50
51    pub fn push(&mut self, msr_type: MeasurementType, msr_value: f64) {
52        self.data.insert(msr_type, msr_value);
53    }
54
55    pub fn with(mut self, msr_type: MeasurementType, msr_value: f64) -> Self {
56        self.push(msr_type, msr_value);
57        self
58    }
59
60    /// Builds an observation vector for this measurement provided a set of measurement types.
61    /// If the requested measurement type is not available, then that specific row is set to zero.
62    /// The caller must set the appropriate sensitivity matrix rows to zero.
63    pub fn observation<S: DimName>(&self, types: &IndexSet<MeasurementType>) -> OVector<f64, S>
64    where
65        DefaultAllocator: Allocator<S>,
66    {
67        // Consider adding a modulo modifier here, any bias should be configured by each ground station.
68        let mut obs = OVector::zeros();
69        for (i, t) in types.iter().enumerate() {
70            if let Some(msr_value) = self.data.get(t) {
71                obs[i] = *msr_value;
72            }
73        }
74        obs
75    }
76
77    /// Returns a vector specifying which measurement types are available.
78    pub fn availability(&self, types: &IndexSet<MeasurementType>) -> Vec<bool> {
79        let mut rtn = vec![false; types.len()];
80        for (i, t) in types.iter().enumerate() {
81            if self.data.contains_key(t) {
82                rtn[i] = true;
83            }
84        }
85        rtn
86    }
87}
88
89impl fmt::Display for Measurement {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        let msrs = self
92            .data
93            .iter()
94            .map(|(msr_type, msr_value)| format!("{msr_type:?} = {msr_value} {}", msr_type.unit()))
95            .collect::<Vec<String>>()
96            .join(", ");
97
98        write!(f, "{} measured {} on {}", self.tracker, msrs, self.epoch)
99    }
100}
101
102impl PartialEq for Measurement {
103    fn eq(&self, other: &Self) -> bool {
104        self.tracker == other.tracker
105            && self.epoch == other.epoch
106            && self.data.iter().all(|(key, &value)| {
107                if let Some(&other_value) = other.data.get(key) {
108                    (value - other_value).abs() < 1e-10
109                } else {
110                    false
111                }
112            })
113    }
114}