melvin_ob/flight_control/orbit/
index.rs

1use crate::util::Vec2D;
2use chrono::{DateTime, Utc};
3use fixed::types::I32F32;
4
5/// Represents a position in an orbit with associated metadata.
6#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize)]
7pub struct IndexedOrbitPosition {
8    /// The timestamp representing the current time of this position.
9    t: DateTime<Utc>,
10    /// The index representing the current index in the orbits `done`-box.
11    index: usize,
12    /// The 2D positional vector of this point in the orbit.
13    pos: Vec2D<I32F32>,
14    /// The period of the orbit.
15    period: usize,
16}
17
18impl IndexedOrbitPosition {
19    /// Creates a new [`IndexedOrbitPosition`] instance.
20    ///
21    /// # Arguments
22    /// - `index`: The index in the orbit.
23    /// - `period`: The period of the orbit.
24    /// - `pos`: The 2D position vector for this point in the orbit.
25    ///
26    /// # Returns
27    /// A new [`IndexedOrbitPosition`] instance with the current UTC time.
28    pub fn new(index: usize, period: usize, pos: Vec2D<I32F32>) -> Self {
29        Self { t: Utc::now(), index, pos, period }
30    }
31
32    /// Returns the timestamp of the position.
33    pub fn t(&self) -> DateTime<Utc> { self.t }
34
35    /// Returns the 2D positional vector of the orbit.
36    pub fn pos(&self) -> Vec2D<I32F32> { self.pos }
37
38    /// Returns the current index in the orbit.
39    pub fn index(&self) -> usize { self.index }
40
41    /// Returns the period of the orbit.
42    pub fn period(&self) -> usize { self.period }
43
44    /// Calculates the ranges from the current index to now, optionally applying a shift.
45    ///
46    /// # Arguments
47    /// - `shift`: An optional value to adjust the calculation of the current index.
48    ///
49    /// # Returns
50    /// A vector of tuples representing the ranges as `(start, end)` for the orbit indices.
51    pub fn get_ranges_to_now(&self, shift: Option<usize>) -> Vec<(usize, usize)> {
52        let end = {
53            if let Some(sh) = shift {
54                (((self.index_now() - sh) % self.period) + self.period) % self.period
55            } else {
56                self.index_now() % self.period
57            }
58        };
59        if end < self.index {
60            vec![(self.index, self.period), (0, end)]
61        } else {
62            vec![(self.index, self.index_now() % self.period)]
63        }
64    }
65
66    /// Maps ranges of indices, ensuring they wrap around correctly for a maximum range.
67    ///
68    /// # Arguments
69    /// - `ranges`: A vector of tuples representing input ranges as `(start, end)`.
70    /// - `max`: The maximum range for the indices.
71    ///
72    /// # Returns
73    /// A vector of tuples representing mapped ranges as `(start, end)` for the orbit indices.
74    #[allow(clippy::cast_sign_loss)]
75    pub fn map_ranges(ranges: &Vec<(isize, isize)>, max: isize) -> Vec<(usize, usize)> {
76        let mut mapped_ranges = Vec::new();
77        for range in ranges {
78            let start = (((range.0 % max) + max) % max) as usize;
79            let end = (((range.1 % max) + max) % max) as usize;
80            if start < end {
81                mapped_ranges.push((start, end));
82            } else {
83                mapped_ranges.push((start, (max - 1) as usize));
84                mapped_ranges.push((0, end));
85            }
86        }
87        mapped_ranges
88    }
89
90    /// Creates a new `IndexedOrbitPosition` with the given 2D position vector.
91    ///
92    /// # Arguments
93    /// - `pos`: The new 2D position vector.
94    ///
95    /// # Returns
96    /// A new `IndexedOrbitPosition` instance with updated position and time.
97    pub fn new_from_pos(&self, pos: Vec2D<I32F32>) -> Self {
98        Self { t: Utc::now(), index: self.index_now(), pos, period: self.period }
99    }
100
101    /// Creates a new `IndexedOrbitPosition` with a future timestamp and given 2D position vector.
102    ///
103    /// # Arguments
104    /// - `pos`: The new 2D position vector.
105    /// - `dt`: The time difference to calculate the future timestamp.
106    ///
107    /// # Returns
108    /// A new `IndexedOrbitPosition` instance with updated position and future timestamp.
109    pub fn new_from_future_pos(&self, pos: Vec2D<I32F32>, t: DateTime<Utc>) -> Self {
110        Self { t, index: self.index_then(t), pos, period: self.period }
111    }
112
113    /// Calculates the current index in the orbit based on the elapsed time.
114    ///
115    /// # Returns
116    /// The current index in the orbit.
117    #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
118    fn index_now(&self) -> usize {
119        (self.index + (Utc::now() - self.t).num_seconds() as usize) % self.period
120    }
121
122    /// Calculates the index in the orbit for a given time offset.
123    ///
124    /// # Arguments
125    /// - `dt`: The time difference to calculate the future index.
126    ///
127    /// # Returns
128    /// The future index in the orbit.
129    #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
130    pub(crate) fn index_then(&self, t: DateTime<Utc>) -> usize {
131        (self.index + (t - self.t).num_seconds() as usize) % self.period
132    }
133}