1use log::{debug, trace};
17
18#[cfg(feature = "serde")]
19use serde::{Deserialize, Serialize};
20
21use super::{LegPerformance, Performance};
22use crate::aircraft::Aircraft;
23use crate::measurements::Duration;
24use crate::route::Route;
25use crate::{Fuel, VerticalDistance};
26
27#[repr(C)]
28#[derive(Copy, Clone, Eq, PartialEq, Debug)]
29pub enum Reserve {
30 Manual(Duration),
31}
32
33impl Reserve {
34 pub fn fuel(self, perf: &Performance, cruise: &VerticalDistance) -> Fuel {
35 match self {
36 Self::Manual(duration) => perf.ff(cruise) * duration,
37 }
38 }
39}
40
41impl Default for Reserve {
42 fn default() -> Self {
43 Self::Manual(Duration::default())
44 }
45}
46
47#[repr(C)]
54#[derive(Copy, Clone, Eq, PartialEq, Debug)]
55pub enum FuelPolicy {
56 MinimumFuel,
58 MaximumFuel,
60 ManualFuel(Fuel),
62 FuelAtLanding(Fuel),
64 ExtraFuel(Fuel),
66}
67
68#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
69#[derive(Copy, Clone, Eq, PartialEq, Debug)]
70pub struct FuelPlanning {
71 taxi: Fuel,
72 trip: Fuel,
73 alternate: Option<Fuel>,
74 reserve: Fuel,
75 total: Fuel,
76 min: Fuel,
77 extra: Option<Fuel>,
78 after_landing: Fuel,
79}
80
81impl FuelPlanning {
82 pub fn new(
83 aircraft: &Aircraft,
84 policy: &FuelPolicy,
85 taxi: Fuel,
86 route: &Route,
87 reserve: &Reserve,
88 perf: &LegPerformance,
89 ) -> Option<Self> {
90 let trip = *route.totals(Some(perf))?.fuel()?.total();
91
92 let last_level = route.legs().last().and_then(|l| l.level()).cloned()?;
94
95 let alternate = route
96 .alternate()
97 .and_then(|alternate| alternate.fuel(perf))
98 .map(|lf| *lf.total());
99 let reserve = reserve.fuel(perf.cruise()?, &last_level);
100
101 trace!(
102 "fuel planning: trip={:.1}, alternate={:.1?}, reserve={:.1?}",
103 trip,
104 alternate,
105 reserve
106 );
107
108 let min = {
109 let mut min = taxi + trip + reserve;
110
111 if let Some(alternate) = alternate {
112 min = min + alternate;
113 }
114
115 min
116 };
117
118 let extra = {
119 match policy {
120 FuelPolicy::MinimumFuel => None,
121 FuelPolicy::MaximumFuel => {
122 aircraft.usable_fuel().map(|usable_fuel| usable_fuel - min)
123 }
124 FuelPolicy::ManualFuel(fuel) => Some(*fuel - min),
125 FuelPolicy::FuelAtLanding(fuel) => Some(*fuel), FuelPolicy::ExtraFuel(fuel) => Some(*fuel),
127 }
128 };
129
130 let total = {
131 match extra {
132 Some(extra) => min + extra,
133 None => min,
134 }
135 };
136
137 let after_landing = total - taxi - trip;
138
139 debug!(
140 "fuel planning: min={:.1}, total={:.1}, extra={:.1?}, after_landing={:.1}",
141 min, total, extra, after_landing
142 );
143
144 Some(Self {
145 taxi,
146 trip,
147 alternate,
148 reserve,
149 total,
150 min,
151 extra,
152 after_landing,
153 })
154 }
155
156 pub fn taxi(&self) -> &Fuel {
157 &self.taxi
158 }
159
160 pub fn trip(&self) -> &Fuel {
161 &self.trip
162 }
163
164 pub fn alternate(&self) -> Option<&Fuel> {
165 self.alternate.as_ref()
166 }
167
168 pub fn reserve(&self) -> &Fuel {
169 &self.reserve
170 }
171
172 pub fn total(&self) -> &Fuel {
173 &self.total
174 }
175
176 pub fn min(&self) -> &Fuel {
177 &self.min
178 }
179
180 pub fn extra(&self) -> Option<&Fuel> {
181 self.extra.as_ref()
182 }
183
184 pub fn on_ramp(&self) -> &Fuel {
185 &self.total
186 }
187
188 pub fn after_landing(&self) -> &Fuel {
189 &self.after_landing
190 }
191}