FlightGear next
atmosphere.cxx
Go to the documentation of this file.
1#include <simgear/math/SGMath.hxx>
2#include <simgear/debug/logstream.hxx>
3
4#include "atmosphere.hxx"
5
6using namespace std;
7#include <iostream>
8#include <cstdio>
9
10const ISA_layer ISA_def[] = {
11// 0 1 2 3 4 5 6 7 8
12// id (m) (ft) (Pa) (inHg) (K) (C) (K/m) (K/ft)
13ISA_layer(0, 0, 0, 101325, 29.92126, 288.15, 15.00, 0.0065, 0.0019812),
14ISA_layer(1, 11000, 36089, 22632.1, 6.683246, 216.65, -56.50, 0, 0),
15ISA_layer(2, 20000, 65616, 5474.89, 1.616734, 216.65, -56.50, -0.0010, -0.0003048),
16ISA_layer(3, 32000, 104986, 868.019, 0.256326, 228.65, -44.50, -0.0028, -0.0008534),
17ISA_layer(4, 47000, 154199, 110.906, 0.0327506, 270.65, -2.50, 0, 0),
18ISA_layer(5, 51000, 167322, 66.9389, 0.0197670, 270.65, -2.50, 0.0028, 0.0008534),
19ISA_layer(6, 71000, 232939, 3.95642, 0.00116833, 214.65, -58.50, 0.0020, 0.0006096),
20ISA_layer(7, 80000, 262467, 0.88628, 0.000261718, 196.65, -76.50, 0.0, 0.0),
21// The last layer MUST have -1.0 for its 'lapse' field
22ISA_layer(8, 1.0e9, 3.28e9, 0.00001, 3.0e-9, 2.73, -270.4, -1.0)
23};
24
25// Pressure within a layer, as a function of height.
26// Physics model: standard or nonstandard atmosphere,
27// depending on what parameters you pass in.
28// Height in meters, pressures in pascals.
29// As always, lapse is positive in the troposphere,
30// and zero in the first part of the stratosphere.
31
32double P_layer(const double height, const double href,
33 const double Pref, const double Tref,
34 const double lapse) {
35 using namespace atmodel;
36 if (lapse) {
37 double N = lapse * Rgas / mm / g;
38 return Pref * pow( (Tref - lapse*(height - href)) / Tref , (1/N));
39 } else {
40 return Pref * exp(-g * mm / Rgas / Tref * (height - href));
41 }
42}
43
44
45// Temperature within a layer, as a function of height.
46// Physics model: standard or nonstandard atmosphere
47// depending on what parameters you pass in.
48// $hh in meters, pressures in Pa.
49// As always, $lambda is positive in the troposphere,
50// and zero in the first part of the stratosphere.
51double T_layer (
52 const double hh,
53 const double hb,
54 const double Pb,
55 const double Tb,
56 const double lambda) {
57 return Tb - lambda*(hh - hb);
58}
59
60// Pressure and temperature as a function of height, Psl, and Tsl.
61// heights in meters, pressures in Pa.
62// Daisy chain version.
63// We need "seed" values for sea-level pressure and temperature.
64// In addition, for every layer, we need three things
65// from the table: the reference height in that layer,
66// the lapse in that layer, and the cap (if any) for that layer
67// (which we take from the /next/ row of the table, if any).
68pair<double,double> PT_vs_hpt(
69 const double hh,
70 const double _p0,
71 const double _t0
72) {
73
74 const double d0(0);
75 double hgt = ISA_def[0].height;
76 double p0 = _p0;
77 double t0 = _t0;
78#if 0
79 cout << "PT_vs_hpt: " << hh << " " << p0 << " " << t0 << endl;
80#endif
81
82 int ii = 0;
83 for (const ISA_layer* pp = ISA_def; pp->lapse != -1; pp++, ii++) {
84#if 0
85 cout << "PT_vs_hpt: " << ii
86 << " height: " << pp->height
87 << " temp: " << pp->temp
88 << " lapse: " << pp->lapse
89 << endl;
90#endif
91 double xhgt(9e99);
92 double lapse = pp->lapse;
93// Stratosphere starts at a definite temperature,
94// not a definite height:
95 if (ii == 0) {
96 xhgt = hgt + (t0 - (pp+1)->temp) / lapse;
97 } else if ((pp+1)->lapse != -1) {
98 xhgt = (pp+1)->height;
99 }
100 if (hh <= xhgt) {
101 return make_pair(P_layer(hh, hgt, p0, t0, lapse),
102 T_layer(hh, hgt, p0, t0, lapse));
103 }
104 p0 = P_layer(xhgt, hgt, p0, t0, lapse);
105 t0 = t0 - lapse * (xhgt - hgt);
106 hgt = xhgt;
107 }
108
109// Should never get here.
110 SG_LOG(SG_ENVIRONMENT, SG_ALERT, "PT_vs_hpt: ran out of layers for h=" << hh );
111 return make_pair(d0, d0);
112}
113
114
116 a_tvs_p(0)
117{}
118
120 delete a_tvs_p;
121}
122
123
125// The following two routines are called "fake" because they
126// bypass the exceedingly complicated layer model implied by
127// the "weather conditioins" popup menu.
128// For now we must bypass it for several reasons, including
129// the fact that we don't have an "environment" object for
130// the airport (only for the airplane).
131// degrees C, height in feet
132double FGAtmo::fake_T_vs_a_us(const double h_ft,
133 const double Tsl) const {
134 using namespace atmodel;
135 return Tsl - ISA::lam0 * h_ft * foot;
136}
137
138// Dewpoint. degrees C or K, height in feet
139double FGAtmo::fake_dp_vs_a_us(const double dpsl, const double h_ft) {
140 const double dp_lapse(0.002); // [K/m] approximate
141 // Reference: http://en.wikipedia.org/wiki/Lapse_rate
142 return dpsl - dp_lapse * h_ft * atmodel::foot;
143}
144
145// Height as a function of pressure.
146// Valid in the troposphere only.
147double FGAtmo::a_vs_p(const double press, const double qnh) {
148 using namespace atmodel;
149 using namespace ISA;
150 double nn = lam0 * Rgas / g / mm;
151 return T0 * ( pow(qnh/P0,nn) - pow(press/P0,nn) ) / lam0;
152}
153
154
155double FGAtmo::ISATemperatureKAtAltitudeFt(const double alt, const double Tsl)
156{
157 double pressure, temp;
158 std::tie(pressure, temp) = PT_vs_hpt(alt * SG_FEET_TO_METER, atmodel::ISA::P0, Tsl);
159 return temp;
160}
161
162double FGAtmo::CSMetersPerSecondAtAltitudeFt(const double alt, const double Tsl)
163{
164 const double oatK = ISATemperatureKAtAltitudeFt(alt, Tsl);
165 // calculate the speed of sound at altitude
166 const double g = 1.4; // specific heat ratio of air
167 const double R = 287.053; // specific gas constant (J/kgK)
168 return sqrt (g * R * oatK);
169}
170
171double FGAtmo::densityAtAltitudeFt(const double alt, const double Tsl)
172{
173 double pressure, temp;
174 std::tie(pressure, temp) = PT_vs_hpt(alt * SG_FEET_TO_METER, atmodel::ISA::P0, Tsl);
175 const double R = 287.053; // specific gas constant (J/kgK)
176 return pressure / (temp * R);
177}
178
179double FGAtmo::machFromKnotsAtAltitudeFt(const double knots,
180 const double altFt,
181 const double Tsl)
182{
183 const auto cs = CSMetersPerSecondAtAltitudeFt(altFt, Tsl);
184 return (knots * SG_KT_TO_MPS) / cs;
185}
186
187double FGAtmo::knotsFromMachAtAltitudeFt(const double mach,
188 const double altFt,
189 const double Tsl)
190{
191 const auto cs = CSMetersPerSecondAtAltitudeFt(altFt, Tsl);
192 return mach * cs * SG_MPS_TO_KT;
193}
194
195// force retabulation
197 using namespace atmodel;
198 delete a_tvs_p;
199 a_tvs_p = new SGInterpTable;
200
201 for (double hgt = -1000; hgt <= 32000;) {
202 double press,temp;
203 std::tie(press, temp) = PT_vs_hpt(hgt);
204 a_tvs_p->addEntry(press / inHg, hgt / foot);
205
206#ifdef DEBUG_EXPORT_P_H
207 char buf[100];
208 char* fmt = " { %9.2f , %5.0f },";
209 if (press < 10000) fmt = " { %9.3f , %5.0f },";
210 snprintf(buf, 100, fmt, press, hgt);
211 cout << buf << endl;
212#endif
213 if (hgt < 6000) {
214 hgt += 500;
215 } else {
216 hgt += 1000;
217 }
218 }
219}
220
221// make sure cache is valid
223 if (!a_tvs_p)
224 tabulate();
225}
226
227// Check the basic function,
228// then compare against the interpolator.
230 double hgts[] = {
231 -1000,
232 -250,
233 0,
234 250,
235 1000,
236 5250,
237 11000,
238 11000.00001,
239 15500,
240 20000,
241 20000.00001,
242 25500,
243 32000,
244 32000.00001,
245 -9e99
246 };
247
248 for (int i = 0; ; i++) {
249 double height = hgts[i];
250 if (height < -1e6)
251 break;
252 using namespace atmodel;
253 cache();
254 double press,temp;
255 std::tie(press, temp) = PT_vs_hpt(height);
256 cout << "Height: " << height
257 << " \tpressure: " << press << endl;
258 cout << "Check: "
259 << a_tvs_p->interpolate(press / inHg)*foot << endl;
260 }
261}
262
264
269
270double FGAltimeter::reading_ft(const double p_inHg, const double set_inHg) {
271 using namespace atmodel;
272 double press_alt = a_tvs_p->interpolate(p_inHg);
273 double kollsman_shift = a_tvs_p->interpolate(set_inHg);
274 return (press_alt - kollsman_shift);
275}
276
277// Altimeter setting _in pascals_
278// ... caller gets to convert to inHg or millibars
279// Field elevation in m
280// Field pressure in pascals
281// Valid for fields within the troposphere only.
282double FGAtmo::QNH(const double field_elev, const double field_press) {
283 using namespace atmodel;
284
285 // Equation derived in altimetry.htm
286 // exponent in QNH equation:
287 double nn = ISA::lam0 * Rgas / g / mm;
288 // pressure ratio factor:
289 double prat = pow(ISA::P0 / field_press, nn);
290 double rslt = field_press
291 * pow(1. + ISA::lam0 * field_elev / ISA::T0 * prat, 1./nn);
292#if 0
293 SG_LOG(SG_ENVIRONMENT, SG_ALERT, "QNH: elev: " << field_elev
294 << " press: " << field_press
295 << " prat: " << prat
296 << " rslt: " << rslt
297 << " inHg: " << inHg
298 << " rslt/inHG: " << rslt/inHg);
299#endif
300 return rslt;
301}
302
303// Invert the QNH calculation to get the field pressure from a metar
304// report.
305// field pressure _in pascals_
306// ... caller gets to convert to inHg or millibars
307// Field elevation in m
308// Altimeter setting (QNH) in pascals
309// Valid for fields within the troposphere only.
310double FGAtmo::fieldPressure(const double field_elev, const double qnh)
311{
312 using namespace atmodel;
313 static const double nn = ISA::lam0 * Rgas / g / mm;
314 const double pratio = pow(qnh / ISA::P0, nn);
315 return ISA::P0 * pow(pratio - field_elev * ISA::lam0 / ISA::T0, 1.0 / nn);
316}
317
318void FGAltimeter::dump_stack1(const double Tref) {
319 using namespace atmodel;
320 const int bs(200);
321 char buf[bs];
322 double Psl = P_layer(0, 0, ISA::P0, Tref, ISA::lam0);
323 snprintf(buf, bs, "Tref: %6.2f Psl: %5.0f = %7.4f",
324 Tref, Psl, Psl / inHg);
325 cout << buf << endl;
326
327 snprintf(buf, bs,
328 " %6s %6s %6s %6s %6s %6s %6s",
329 "A", "Aind", "Apr", "Aprind", "P", "Psl", "Qnh");
330 cout << buf << endl;
331
332 double hgts[] = {0, 2500, 5000, 7500, 10000, -9e99};
333 for (int ii = 0; ; ii++) {
334 double hgt_ft = hgts[ii];
335 double hgt = hgt_ft * foot;
336 if (hgt_ft < -1e6)
337 break;
338 double press = P_layer(hgt, 0, ISA::P0, Tref, ISA::lam0);
339 double qnhx = QNH(hgt, press) / inHg;
340 double qnh2 = SGMiscd::round(qnhx*100)/100;
341
342 double p_inHg = press / inHg;
343 double Aprind = reading_ft(p_inHg);
344 double Apr = a_vs_p(p_inHg*inHg) / foot;
345 double hind = reading_ft(p_inHg, qnh2);
346 snprintf(buf, bs,
347 " %6.0f %6.0f %6.0f %6.0f %6.2f %6.2f %6.2f",
348 hgt_ft, hind, Apr, Aprind, p_inHg, Psl/inHg, qnh2);
349 cout << buf << endl;
350 }
351}
352
353
355 using namespace atmodel;
356 cout << "........." << endl;
357 cout << "Size: " << sizeof(FGAtmo) << endl;
359 dump_stack1(ISA::T0 - 20);
360}
#define i(x)
const ISA_layer ISA_def[]
double T_layer(const double hh, const double hb, const double Pb, const double Tb, const double lambda)
double P_layer(const double height, const double href, const double Pref, const double Tref, const double lapse)
pair< double, double > PT_vs_hpt(const double hh, const double _p0, const double _t0)
double P_layer(const double height, const double href, const double Pref, const double Tref, const double lapse)
std::pair< double, double > PT_vs_hpt(const double hh, const double _p0=atmodel::ISA::P0, const double _t0=atmodel::ISA::T0)
void dump_stack1(const double Tref)
double reading_ft(const double p_inHg, const double set_inHg=atmodel::ISA::P0/atmodel::inHg)
void dump_stack()
void tabulate()
void check_model()
double QNH(const double field_elev, const double field_press)
static double densityAtAltitudeFt(const double alt, const double Tsl=atmodel::ISA::T0)
double a_vs_p(const double press, const double qnh=atmodel::ISA::P0)
static double ISATemperatureKAtAltitudeFt(const double alt, const double Tsl=atmodel::ISA::T0)
Compute the outisde temperature at an altitude, according to the standard atmosphere model.
static double knotsFromMachAtAltitudeFt(const double mach, const double altFt, const double Tsl=atmodel::ISA::T0)
static double CSMetersPerSecondAtAltitudeFt(const double alt, const double Tsl=atmodel::ISA::T0)
Compute the speed of sound at an altitude.
static double fieldPressure(const double field_elev, const double qnh)
Invert the QNH calculation to get the field pressure from a metar report.
static double machFromKnotsAtAltitudeFt(const double knots, const double altFt, const double Tsl=atmodel::ISA::T0)
double fake_T_vs_a_us(const double h_ft, const double Tsl=atmodel::ISA::T0) const
double fake_dp_vs_a_us(const double dpsl, const double h_ft)
#define N
#define R
const double lam0(.0065)
const double P0(101325.0)
const double T0(15.+freezing)
const double g(9.80665)
const double inHg(101325.0/760 *1000 *inch)
const double Rgas(8.31432)
const double mm(.0289644)
const double foot(12 *inch)