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}
38
39impl Measurement {
40 pub fn new(tracker: String, epoch: Epoch) -> Self {
41 Self {
42 tracker,
43 epoch,
44 data: IndexMap::new(),
45 }
46 }
47
48 pub fn push(&mut self, msr_type: MeasurementType, msr_value: f64) {
49 self.data.insert(msr_type, msr_value);
50 }
51
52 pub fn with(mut self, msr_type: MeasurementType, msr_value: f64) -> Self {
53 self.push(msr_type, msr_value);
54 self
55 }
56
57 /// Builds an observation vector for this measurement provided a set of measurement types.
58 /// If the requested measurement type is not available, then that specific row is set to zero.
59 /// The caller must set the appropriate sensitivity matrix rows to zero.
60 pub fn observation<S: DimName>(&self, types: &IndexSet<MeasurementType>) -> OVector<f64, S>
61 where
62 DefaultAllocator: Allocator<S>,
63 {
64 // Consider adding a modulo modifier here, any bias should be configured by each ground station.
65 let mut obs = OVector::zeros();
66 for (i, t) in types.iter().enumerate() {
67 if let Some(msr_value) = self.data.get(t) {
68 obs[i] = *msr_value;
69 }
70 }
71 obs
72 }
73
74 /// Returns a vector specifying which measurement types are available.
75 pub fn availability(&self, types: &IndexSet<MeasurementType>) -> Vec<bool> {
76 let mut rtn = vec![false; types.len()];
77 for (i, t) in types.iter().enumerate() {
78 if self.data.contains_key(t) {
79 rtn[i] = true;
80 }
81 }
82 rtn
83 }
84}
85
86impl fmt::Display for Measurement {
87 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88 let msrs = self
89 .data
90 .iter()
91 .map(|(msr_type, msr_value)| format!("{msr_type:?} = {msr_value} {}", msr_type.unit()))
92 .collect::<Vec<String>>()
93 .join(", ");
94
95 write!(f, "{} measured {} on {}", self.tracker, msrs, self.epoch)
96 }
97}
98
99impl PartialEq for Measurement {
100 fn eq(&self, other: &Self) -> bool {
101 self.tracker == other.tracker
102 && self.epoch == other.epoch
103 && self.data.iter().all(|(key, &value)| {
104 if let Some(&other_value) = other.data.get(key) {
105 (value - other_value).abs() < 1e-10
106 } else {
107 false
108 }
109 })
110 }
111}