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}