FlightGear next
AircraftPerformance.cxx
Go to the documentation of this file.
1// AircraftPerformance.cxx - compute data about planned acft performance
2//
3// Copyright (C) 2018 James Turner <james@flightgear.org>
4// This program is free software; you can redistribute it and/or
5// modify it under the terms of the GNU General Public License as
6// published by the Free Software Foundation; either version 2 of the
7// License, or (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program; if not, write to the Free Software
16// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
19
20#include <cassert>
21#include <algorithm>
22
23#include <simgear/constants.h>
24
25#include <Main/fg_props.hxx>
26
27using namespace flightgear;
28
29double distanceForTimeAndSpeeds(double tSec, double v1, double v2)
30{
31 return tSec * 0.5 * (v1 + v2);
32}
33
35{
36 // read aircraft supplied performance data
37 if (fgGetNode("/aircraft/performance/bracket")) {
38 readPerformanceData();
39 } else {
40 // falls back to heuristic determination of the category,
41 // and a plausible default
42 icaoCategoryData();
43 }
44}
45
47{
48 auto bracket = bracketForAltitude(altitudeFt);
49 return bracket->gsForAltitude(altitudeFt);
50}
51
52int AircraftPerformance::computePreviousAltitude(double distanceM, int targetAltFt) const
53{
54 auto bracket = bracketForAltitude(targetAltFt);
55 auto d = bracket->descendDistanceM(bracket->atOrBelowAltitudeFt, targetAltFt);
56 if (d < distanceM) {
57 // recurse to previous bracket
58 return computePreviousAltitude(distanceM - d, bracket->atOrBelowAltitudeFt+1);
59 }
60
61 // work out how far we travel laterally per foot change in altitude
62 // this value is in metres, we have to map FPM and GS in Knots to make
63 // everything work out
64 const double gsMPS = bracket->gsForAltitude(targetAltFt) * SG_KT_TO_MPS;
65 const double t = distanceM / gsMPS;
66 return targetAltFt + bracket->descentRateFPM * (t / 60.0);
67}
68
69int AircraftPerformance::computeNextAltitude(double distanceM, int initialAltFt) const
70{
71 auto bracket = bracketForAltitude(initialAltFt);
72 auto d = bracket->climbDistanceM(initialAltFt, bracket->atOrBelowAltitudeFt);
73 if (d < distanceM) {
74 // recurse to next bracket
75 return computeNextAltitude(distanceM - d, bracket->atOrBelowAltitudeFt+1);
76 }
77
78 // work out how far we travel laterally per foot change in altitude
79 // this value is in metres, we have to map FPM and GS in Knots to make
80 // everything work out
81 const double gsMPS = bracket->gsForAltitude(initialAltFt) * SG_KT_TO_MPS;
82 const double t = distanceM / gsMPS;
83 return initialAltFt + bracket->climbRateFPM * (t / 60.0);
84}
85
87{
89 const auto tagsNode = fgGetNode("/sim/tags");
90 if (!tagsNode)
91 return r;
92
93 for (auto t : tagsNode->getChildren("tag")) {
94 r.push_back(t->getStringValue());
95 }
96 return r;
97}
98
99static bool stringListContains(const string_list& t, const std::string& s)
100{
101 auto it = std::find(t.begin(), t.end(), s);
102 return it != t.end();
103}
104
105std::string AircraftPerformance::heuristicCatergoryFromTags() const
106{
107 const auto tags(readTags());
108
109 if (stringListContains(tags, "turboprop"))
111
112 // any way we could distuinguish fast and slow GA aircraft?
113 if (stringListContains(tags, "ga")) {
115 }
116
117 if (stringListContains(tags, "jet")) {
119 }
120
122}
123
124void AircraftPerformance::icaoCategoryData()
125{
126 std::string propCat = fgGetString("/aircraft/performance/icao-category");
127 if (propCat.empty()) {
128 propCat = heuristicCatergoryFromTags();
129 }
130
131 const char aircraftCategory = propCat.front();
132 // pathTurnRate = 3.0; // 3 deg/sec = 180deg/min = standard rate turn
133 switch (aircraftCategory) {
135 _perfData.push_back(Bracket(4000, 600, 1200, 75));
136 _perfData.push_back(Bracket(10000, 600, 1200, 140));
137 break;
138
140 _perfData.push_back(Bracket(4000, 100, 1200, 100));
141 _perfData.push_back(Bracket(10000, 800, 1200, 160));
142 _perfData.push_back(Bracket(18000, 600, 1800, 200));
143 break;
144
146 _perfData.push_back(Bracket(4000, 1800, 1800, 150));
147 _perfData.push_back(Bracket(10000, 1800, 1800, 200));
148 _perfData.push_back(Bracket(18000, 1200, 1800, 270));
149 _perfData.push_back(Bracket(60000, 800, 1200, 0.80, true /* is Mach */));
150 break;
151
154 default:
155 _perfData.push_back(Bracket(4000, 1800, 1800, 180));
156 _perfData.push_back(Bracket(10000, 1800, 1800, 230));
157 _perfData.push_back(Bracket(18000, 1200, 1800, 270));
158 _perfData.push_back(Bracket(60000, 800, 1200, 0.87, true /* is Mach */));
159 break;
160 }
161}
162
163void AircraftPerformance::readPerformanceData()
164{
165 for (auto nd : fgGetNode("/aircraft/performance/")->getChildren("bracket")) {
166 const int atOrBelowAlt = nd->getIntValue("at-or-below-ft");
167 const int climbFPM = nd->getIntValue("climb-rate-fpm");
168 const int descentFPM = nd->getIntValue("descent-rate-fpm");
169 bool isMach = nd->hasChild("speed-mach");
170 double speed;
171 if (isMach) {
172 speed = nd->getDoubleValue("speed-mach");
173 } else {
174 speed = nd->getIntValue("speed-ias-knots");
175 }
176
177 Bracket b(atOrBelowAlt, climbFPM, descentFPM, speed, isMach);
178 _perfData.push_back(b);
179 }
180}
181
182auto AircraftPerformance::bracketForAltitude(int altitude) const
183 -> PerformanceVec::const_iterator
184{
185 assert(!_perfData.empty());
186 if (_perfData.front().atOrBelowAltitudeFt >= altitude)
187 return _perfData.begin();
188
189 for (auto it = _perfData.begin(); it != _perfData.end(); ++it) {
190 if (it->atOrBelowAltitudeFt > altitude) {
191 return it;
192 }
193 }
194
195 return _perfData.end() - 1;
196}
197
198auto AircraftPerformance::rangeForAltitude(int lowAltitude, int highAltitude) const
199 -> BracketRange
200{
201 return {bracketForAltitude(lowAltitude), bracketForAltitude(highAltitude)};
202}
203
204void AircraftPerformance::traverseAltitudeRange(int initialElevationFt, int targetElevationFt,
205 TraversalFunc tf) const
206{
207 auto r = rangeForAltitude(initialElevationFt, targetElevationFt);
208 if (r.first == r.second) {
209 tf(*r.first, initialElevationFt, targetElevationFt);
210 return;
211 }
212
213 if (initialElevationFt < targetElevationFt) {
214 tf(*r.first, initialElevationFt, r.first->atOrBelowAltitudeFt);
215 int previousBracketCapAltitude = r.first->atOrBelowAltitudeFt;
216 for (auto bracket = r.first + 1; bracket != r.second; ++bracket) {
217 tf(*bracket, previousBracketCapAltitude, bracket->atOrBelowAltitudeFt);
218 previousBracketCapAltitude = bracket->atOrBelowAltitudeFt;
219 }
220
221 tf(*r.second, previousBracketCapAltitude, targetElevationFt);
222 } else {
223 int nextBracketCapAlt = (r.first - 1)->atOrBelowAltitudeFt;
224 tf(*r.first, initialElevationFt, nextBracketCapAlt);
225 for (auto bracket = r.first - 1; bracket != r.second; --bracket) {
226 nextBracketCapAlt = (r.first - 1)->atOrBelowAltitudeFt;
227 tf(*bracket, bracket->atOrBelowAltitudeFt, nextBracketCapAlt);
228 }
229
230 tf(*r.second, nextBracketCapAlt, targetElevationFt);
231 }
232}
233
234double AircraftPerformance::distanceNmBetween(int initialElevationFt, int targetElevationFt) const
235{
236 double result = 0.0;
237 TraversalFunc tf = [&result](const Bracket& bk, int alt1, int alt2) {
238 result += (alt1 > alt2) ? bk.descendDistanceM(alt1, alt2) : bk.climbDistanceM(alt1, alt2);
239 };
240 traverseAltitudeRange(initialElevationFt, targetElevationFt, tf);
241 return result * SG_METER_TO_NM;
242}
243
244double AircraftPerformance::timeBetween(int initialElevationFt, int targetElevationFt) const
245{
246 double result = 0.0;
247 TraversalFunc tf = [&result](const Bracket& bk, int alt1, int alt2) {
248 SG_LOG(SG_GENERAL, SG_INFO, "Range:" << alt1 << " " << alt2);
249 result += (alt1 > alt2) ? bk.descendTime(alt1, alt2) : bk.climbTime(alt1, alt2);
250 };
251 traverseAltitudeRange(initialElevationFt, targetElevationFt, tf);
252 return result;
253}
254
255double AircraftPerformance::timeToCruise(double cruiseDistanceNm, int cruiseAltitudeFt) const
256{
257 auto b = bracketForAltitude(cruiseAltitudeFt);
258 return (cruiseDistanceNm / b->gsForAltitude(cruiseAltitudeFt)) * 3600.0;
259}
260
261double oatCForAltitudeFt(int altitudeFt)
262{
263 if (altitudeFt > 36089)
264 return -56.5;
265
266 // lapse rate in C per ft
267 const double T_r = .0019812;
268 return 15.0 - (altitudeFt * T_r);
269}
270
271double oatKForAltitudeFt(int altitudeFt)
272{
273 return oatCForAltitudeFt(altitudeFt) + 273.15;
274}
275
277{
278/*
279 p= P_0*(1-6.8755856*10^-6 h)^5.2558797 h<36,089.24ft
280 p_Tr= 0.2233609*P_0
281 p=p_Tr*exp(-4.806346*10^-5(h-36089.24)) h>36,089.24ft
282
283 magic numbers
284 6.8755856*10^-6 = T'/T_0, where T' is the standard temperature lapse rate and T_0 is the standard sea-level temperature.
285 5.2558797 = Mg/RT', where M is the (average) molecular weight of air, g is the acceleration of gravity and R is the gas constant.
286 4.806346*10^-5 = Mg/RT_tr, where T_tr is the temperature at the tropopause.
287*/
288
289 const double k = 6.8755856e-6;
290 const double MgRT = 5.2558797;
291 const double MgRT_tr = 4.806346e-5;
292 const double P_0 = 29.92126; // (standard) sea-level pressure
293 if (altitude > 36089) {
294 const double P_Tr = 0.2233609 * P_0;
295 const double altAboveTr = altitude - 36089;
296 return P_Tr * exp(MgRT_tr * altAboveTr);
297 } else {
298 return P_0 * pow(1.0 - (k * altitude), MgRT);
299 }
300}
301
302double computeMachFromIAS(int iasKnots, int altitudeFt)
303{
304#if 0
305 // from the aviation formulary
306 DP=P_0*((1 + 0.2*(IAS/CS_0)^2)^3.5 -1)
307 M=(5*( (DP/P + 1)^(2/7) -1) )^0.5 (*)
308#endif
309 const double Cs_0 = 661.4786; // speed of sound at sea level, knots
310 const double P_0 = 29.92126; // (standard) sea-level pressure
311 const double iasCsRatio = iasKnots / Cs_0;
312 const double P = pressureAtAltitude(altitudeFt);
313 // differential pressure
314 const double DP = P_0 * (pow(1.0 + 0.2 * pow(iasCsRatio, 2.0), 3.5) - 1.0);
315
316 const double pressureRatio = DP / P + 1.0;
317 const double M = pow(5.0 * (pow(pressureRatio, 2.0 / 7.0) - 1.0), 0.5);
318 if (M > 1.0) {
319 SG_LOG(SG_GENERAL, SG_INFO, "computeMachFromIAS: computed Mach is supersonic, fix for shock wave");
320 }
321 return M;
322}
323
324double AircraftPerformance::machForCAS(int altitudeFt, double cas)
325{
326 return computeMachFromIAS(static_cast<int>(cas), altitudeFt);
327}
328
329
330double AircraftPerformance::groundSpeedForCAS(int altitudeFt, double cas)
331{
332 return groundSpeedForMach(altitudeFt, computeMachFromIAS(cas, altitudeFt));
333}
334
335double AircraftPerformance::groundSpeedForMach(int altitudeFt, double mach)
336{
337 // CS = sound speed= 38.967854*sqrt(T+273.15) where T is the OAT in celsius.
338 const double CS = 38.967854 * sqrt(oatKForAltitudeFt(altitudeFt));
339 const double TAS = mach * CS;
340 return TAS;
341}
342
343int AircraftPerformance::Bracket::gsForAltitude(int altitude) const
344{
345 double M = 0.0;
346 if (speedIsMach) {
347 M = speedIASOrMach; // simple
348 } else {
349 M = computeMachFromIAS(speedIASOrMach, altitude);
350 }
351
353}
354
355double AircraftPerformance::Bracket::climbTime(int alt1, int alt2) const
356{
357 return (alt2 - alt1) / static_cast<double>(climbRateFPM) * 60.0;
358}
359
360double AircraftPerformance::Bracket::climbDistanceM(int alt1, int alt2) const
361{
362 const double t = climbTime(alt1, alt2);
364 SG_KT_TO_MPS * gsForAltitude(alt1),
365 SG_KT_TO_MPS * gsForAltitude(alt2));
366}
367
368double AircraftPerformance::Bracket::descendTime(int alt1, int alt2) const
369{
370 return (alt1 - alt2) / static_cast<double>(descentRateFPM) * 60.0;
371}
372
373double AircraftPerformance::Bracket::descendDistanceM(int alt1, int alt2) const
374{
375 const double t = descendTime(alt1, alt2);
377 SG_KT_TO_MPS * gsForAltitude(alt1),
378 SG_KT_TO_MPS * gsForAltitude(alt2));
379}
380
382{
383#if 0
384 From the aviation formulary again
385 In a steady turn, in no wind, with bank angle, b at an airspeed v
386
387 tan(b)= v^2/(R g)
388
389 With R in feet, v in knots, b in degrees and w in degrees/sec (inconsistent units!), numerical constants are introduced:
390
391 R =v^2/(11.23*tan(0.01745*b))
392 (Example) At 100 knots, with a 45 degree bank, the radius of turn is 100^2/(11.23*tan(0.01745*45))= 891 feet.
393
394 The bank angle b_s for a standard rate turn is given by:
395
396 b_s = 57.3*atan(v/362.1)
397 (Example) for 100 knots, b_s = 57.3*atan(100/362.1) = 15.4 degrees
398
399 Working in meter-per-second and radians removes a bunch of constants again.
400#endif
401 const double gsKts = groundSpeedForAltitudeKnots(altitudeFt);
402 const double gs = gsKts * SG_KT_TO_MPS;
403 const double bankAngleRad = atan(gsKts/362.1);
404 const double r = (gs * gs)/(SG_g0_m_p_s2 * tan(bankAngleRad));
405 return r;
406}
407
double altitude
Definition ADA.cxx:46
double oatCForAltitudeFt(int altitudeFt)
double distanceForTimeAndSpeeds(double tSec, double v1, double v2)
double oatKForAltitudeFt(int altitudeFt)
static bool stringListContains(const string_list &t, const std::string &s)
double computeMachFromIAS(int iasKnots, int altitudeFt)
static string_list readTags()
double pressureAtAltitude(int altitude)
for runways and taxiways.
static double groundSpeedForCAS(int altitudeFt, double cas)
static double groundSpeedForMach(int altitudeFt, double mach)
double timeBetween(int initialElevationFt, int targetElevationFt) const
double groundSpeedForAltitudeKnots(int altitudeFt) const
int computeNextAltitude(double distanceM, int initialAltFt) const
double distanceNmBetween(int initialElevationFt, int targetElevationFt) const
double timeToCruise(double cruiseDistanceNm, int cruiseAltitudeFt) const
int computePreviousAltitude(double distanceM, int targetAltFt) const
static double machForCAS(int altitudeFt, double cas)
double turnRadiusMForAltitude(int altitudeFt) const
std::string fgGetString(const char *name, const char *defaultValue)
Get a string value for a property.
Definition fg_props.cxx:556
std::vector< std::string > string_list
Definition globals.hxx:36
#define M
#define R
FlightPlan.hxx - defines a full flight-plan object, including departure, cruise, arrival information ...
Definition Addon.cxx:53
const char ICAO_AIRCRAFT_CATEGORY_B
const char ICAO_AIRCRAFT_CATEGORY_E
const char ICAO_AIRCRAFT_CATEGORY_C
const char ICAO_AIRCRAFT_CATEGORY_A
const char ICAO_AIRCRAFT_CATEGORY_D
SGPropertyNode * fgGetNode(const char *path, bool create)
Get a property node.
Definition proptest.cpp:27