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}