capra_core/common/
dive_segment.rs

1use crate::common::dive_segment::DiveSegmentError::IncorrectSegmentTypeError;
2use crate::common::dive_segment::SegmentType::AscDesc;
3use crate::common::mtr_bar;
4use time::Duration;
5
6/// Represents errors that occur while working with DiveSegments.
7#[derive(thiserror::Error, Debug)]
8pub enum DiveSegmentError {
9    #[error("segment type and start/end depths are inconsistent")]
10    /// SegmentType supplied to create a DiveSegment were inconsistent with its parameters.
11    IncorrectSegmentTypeError,
12}
13
14/// Represents different types of DiveSegments possible.
15#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
16#[cfg_attr(feature = "use-serde", derive(serde::Serialize, serde::Deserialize))]
17pub enum SegmentType {
18    /// Segment represents a no decompression limit.
19    NoDeco,
20    /// Segment represents a mandatory decompression stop.
21    DecoStop,
22    /// Segment represents a bottom segment.
23    DiveSegment,
24    /// Segment represents a change in depth.
25    AscDesc,
26}
27
28/// The atomic unit of a dive. Every dive can be represented by a list of DiveSegments.
29#[derive(Debug, Copy, Clone)]
30#[cfg_attr(feature = "use-serde", derive(serde::Serialize, serde::Deserialize))]
31pub struct DiveSegment {
32    /// Type of this segment. See [`SegmentType`].
33    segment_type: SegmentType,
34    /// Depth at the beginning of segment.
35    start_depth: usize,
36    /// Depth at the end of segment.
37    end_depth: usize,
38    /// Duration of the segment.
39    time: Duration,
40    /// Ascent rate (measured in m min^-1)
41    ascent_rate: isize,
42    /// Descent rate (measured in m min^-1)
43    descent_rate: isize,
44}
45
46impl DiveSegment {
47    /// Returns a new DiveSegment with the given parameters.
48    /// # Arguments
49    /// * `segment_type` - Type of this segment. See [`SegmentType`].
50    /// * `start_depth`- Depth at the beginning of the segment.
51    /// * `end_depth` - Depth at the end of the segment
52    /// * `time` - Duration of the segment.
53    /// * `ascent_rate` - Ascent rate of the segment (measured in m min^-1)
54    /// * `descent_rate` - Descent rate of the segment (measured in m min^-1)
55    /// # Errors
56    /// This function will return a [`DiveSegmentError`] if any of the following are true:
57    /// * `segment-type` is `AscDesc` but start and end depths match.
58    /// * `segment-type` is *not* `AscDesc` but start and end depths *do not* match.
59    pub fn new(
60        segment_type: SegmentType,
61        start_depth: usize,
62        end_depth: usize,
63        time: Duration,
64        ascent_rate: isize,
65        descent_rate: isize,
66    ) -> Result<Self, DiveSegmentError> {
67        // Only allow AscDesc segments with a differing start/end depth.
68        // As well as any other segment type without a consistent start/end depth,
69        match (segment_type, start_depth == end_depth) {
70            (AscDesc, true) => return Err(IncorrectSegmentTypeError),
71            (AscDesc, false) => {} // Needed to completely match AscDesc
72            (_, false) => return Err(IncorrectSegmentTypeError),
73            _ => {}
74        }
75
76        Ok(Self {
77            segment_type,
78            start_depth,
79            end_depth,
80            time,
81            ascent_rate,
82            descent_rate,
83        })
84    }
85
86    /// Returns the type of the segment.
87    pub fn segment_type(&self) -> SegmentType {
88        self.segment_type
89    }
90
91    /// Returns the start depth of the segment.
92    pub fn start_depth(&self) -> usize {
93        self.start_depth
94    }
95
96    /// Returns the end depth of the segment.
97    pub fn end_depth(&self) -> usize {
98        self.end_depth
99    }
100
101    /// Returns the duration of the segment.
102    pub fn time(&self) -> &Duration {
103        &self.time
104    }
105
106    /// Returns the ascent rate of the segment.
107    pub fn ascent_rate(&self) -> isize {
108        self.ascent_rate
109    }
110
111    /// Returns the descent rate of the segment.
112    pub fn descent_rate(&self) -> isize {
113        self.descent_rate
114    }
115
116    /// Returns the quantity of gas a diver would consume in the segment.
117    /// # Arguments
118    /// * `sac_rate` - Surface Air Consumption (SAC) rate (measured in bar min^-1).
119    /// * `metres_per_bar` - Depth of water required to induce 1 bar of pressure.
120    pub fn gas_consumed(&self, sac_rate: usize, metres_per_bar: f64) -> usize {
121        let pressure = match self.segment_type() {
122            AscDesc => mtr_bar(
123                ((self.end_depth() + self.start_depth()) / 2) as f64,
124                metres_per_bar,
125            ),
126            _ => mtr_bar(self.end_depth() as f64, metres_per_bar),
127        };
128
129        (pressure * (self.time().as_seconds_f64() / 60.0) * sac_rate as f64) as usize
130    }
131}