// @flow
/**
* @file Atmospheric, thermodynamic, and aerodynamic
* @description Uses a combination of US Standard Atmospheres for 1962 and 1976
* @author Jason Wohlgemuth
* @module atmosphere
* @see Atmospheric and Space Flight Dynamics: Modeling and Simulation with MATLAB and Simulink
* (pages 226, 228-229)
* [citation]{@link https://dl.acm.org/citation.cfm?id=1534352&preflayout=flat}
**/
const partial = require('lodash/partial');
const lte = require('lodash/lte');
const includes = require('lodash/includes');
const range = require('lodash/range');
const findKey = require('lodash/findKey');
const {delta} = require('./math');
const {sqrt} = Math;
/**
* @constant SPECIFIC_HEAT_RATIO Specific heat ratio at sea-level
**/
const SPECIFIC_HEAT_RATIO = 1.405;
/**
* @constant R Sea-level gas constant for air (J/kg*K)
**/
const R = 287;
/**
* @constant NUMBER_OF_LAYERS Number of layers in atmospheric strata
**/
const NUMBER_OF_LAYERS = 21;
/**
* @namespace Atmospheric Strata
* @description Altitude ranges for each atmospheric strata (in km)
**/
const ATMOSPHERIC_STRATA = {/* eslint-disable no-magic-numbers */
troposphere: [0, 11],
stratosphere: [11, 47],
mesosphere: [47, 86],
thermosphere: [86, 500],
exosphere: [500, 10000]
};/* eslint-enable no-magic-numbers */
module.exports = {
getKineticTemperature,
getMolecularTemperature,
getSpeedOfSound,
getStrata,
metersPerSecondToMach,
molecularWeight
};
/**
* @function getStrata
* @description Get atomospheric strata for a given altitude
* @param {number} altitude Altitude (in km)
* @returns {string} The name of the strata that contains the altitude
**/
function getStrata(altitude: number): string {
return findKey(ATMOSPHERIC_STRATA, val => val[0] <= altitude && altitude <= val[1]);
}
/**
* @description Get molecular weight based on layer
* @param {number} i Refers to the quantities at the base of the layer
* @returns {number} molecular weight (in g/mole)
**/
function molecularWeight(i: number): number {
const MOLECULAR_WEIGHT_AT_SEA_LEVEL = 28.964;
const M = [/* eslint-disable no-magic-numbers */
MOLECULAR_WEIGHT_AT_SEA_LEVEL,
28.964,
28.964,
28.964,
28.964,
28.964,
28.962,
28.962,
28.880,
28.560,
28.070,
26.920,
26.660,
26.500,
25.850,
24.690,
22.660,
19.940,
17.940,
16.840,
16.170,
16.17
];/* eslint-enable no-magic-numbers */
return includes(range(M.length), i) ? M[i] : MOLECULAR_WEIGHT_AT_SEA_LEVEL;
}
/**
* @private
* @description Altitude with linear variation
* @memberof module:atmosphere
* @param {number} i Refers to the quantities at the base of the layer
* @returns {number} altitude (in meters)
**/
function h(i: number): number {
const h = [/* eslint-disable no-magic-numbers */
0,
11019.1,
20063.1,
32161.9,
47350.0,
51412.5,
71802.0,
86000.0,
100000,
110000,
120000,
150000,
160000,
170000,
190000,
230000,
300000,
400000,
500000,
600000,
2000000
];/* eslint-enable no-magic-numbers */
return h[i];
}
function getLayerIndex(altitude: number) {
const inLayer = partial(lte, altitude);
return range(NUMBER_OF_LAYERS)
.map(h)
.findIndex(inLayer);
}
/**
* @private
* @description Temperature with linear variation
* @memberof module:atmosphere
* @param {number} i Refers to the quantities at the base of the layer
* @returns {number} temperature (in K)
**/
function temperature(i: number): number {
const T = [/* eslint-disable no-magic-numbers */
288.15,
216.65,
216.65,
228.65,
270.65,
270.65,
214.65,
186.946,
210.02,
257.0,
349.49,
892.79,
1022.2,
1103.4,
1205.4,
1322.3,
1432.1,,
1487.4,
1506.1,
1506.1,
1507.1
];/* eslint-enable no-magic-numbers */
return T[i];
}
/**
* @private
* @description Thermal lapse rate
* @memberof module:atmosphere
* @param {number} i Refers to the quantities at the base of the layer
* @returns {number} rate (in K/km)
**/
function a(i: number): number {
const lapseRates = [/* eslint-disable no-magic-numbers */
-6.5,
0,
1,
2.8,
0,
-2.8,
-2.0,
1.693,
5,
10,
20,
15,
10,
7,
5,
4,
3.3,
2.6,
1.7,
1.1,
0
].map(a => a === 0 ? a : (a / 1000));/* eslint-enable no-magic-numbers */
return lapseRates[i];
}
/**
* @function getMolecularTemperature
* @param {number} altitude
**/
function getMolecularTemperature(altitude: number): number {
const i = getLayerIndex(altitude);
return temperature(i) + (a(i) * (altitude - h(i)));
}
/**
* @function getKineticTemperature
* @param {number} altitude
**/
function getKineticTemperature(altitude: number): number {
const i = getLayerIndex(altitude);
const Mo = molecularWeight(0);
const dM = delta(molecularWeight);
const dh = delta(h);
const Tm = getMolecularTemperature(altitude);
const ratio = molecularWeight(i) + ((dM(i + 1, i) * (altitude - h(i))) / dh(i + 1, i));
return (ratio / Mo) * Tm;
}
/**
* @function getSpeedOfSound
* @description Calculate the speed of sound at a given temparature
* @param {number} [altitude=0]
* @returns {number} speed of sound (in m/s)
* @example <caption>Speed of sound at sea-level</caption>
* const {getSpeedOfSound} = require('applied').atmosphere;
* let speed = getSpeedOfSound();
* console.log(speed);// 340.9 m/s
* @example <caption>Speed of sound at 86 km</caption>
* const {getSpeedOfSound} = require('applied').atmosphere;
* let speed = getSpeedOfSound(86000);
* console.log(speed);// 274.6 m/s
**/
function getSpeedOfSound(altitude: number = 0): number {
const temperature = getKineticTemperature(altitude);
const radicand = SPECIFIC_HEAT_RATIO * R * temperature;
return sqrt(radicand);
}
/**
* @function metersPerSecondToMach
* @param {number} speed Speed in m/s
* @param {number} [altitude]
* @returns {number} Mach number
* @example
* const {getSpeedOfSound, metersPerSecondToMach} = require('applied').atmosphere;
* let speed = getSpeedOfSound();// speed at sea-level (altitude === 0)
* metersPerSecondToMach(speed);// 1
* metersPerSecondToMach(speed, 20000);// 1.15
**/
function metersPerSecondToMach(speed: number, altitude: number): number {
return speed / getSpeedOfSound(altitude);
}