Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ console.log(`Dew Point: ${dewPoint} K`);
| Formula | Description |
|-----------------------------|---------------------------------------------------------------------------|
| **Freezing Level Altitude** | Estimate the altitude where temperature drops below freezing. [🔗](https://en.wikipedia.org/wiki/Freezing_level) |
| **Altitude From Pressure Difference** | Calculate the final altitude from a pressure difference using the hypsometric formula. [🔗](https://en.wikipedia.org/wiki/Hypsometric_equation) |
| **Altitude From Pressure Difference** | Calculate the final altitude from a pressure difference using the hypsometric formula. Supports moist air correction via optional relative humidity parameter using virtual temperature. [🔗](https://en.wikipedia.org/wiki/Hypsometric_equation) |

### Humidity
| Formula | Description |
Expand Down
6 changes: 0 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

54 changes: 48 additions & 6 deletions src/formulas/altitude.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Reading } from '../common';
import * as c from '../constants';
import { saturationVaporPressure, actualVaporPressure, mixingRatio as calcMixingRatio } from './humidity';
import { virtualTemperature } from './temperature';

/**
* Estimate the altitude (in meters) where the temperature drops below freezing (0°C).
Expand All @@ -25,32 +27,72 @@ export function freezingLevelAltitude(
* Given a reference pressure at a known altitude and an observed pressure, this function
* returns the altitude at which the observed pressure occurs.
*
* For dry air conditions, the formula uses the specific gas constant for dry air (287.05 J/(kg·K)).
* For moist air conditions (when relativeHumidity is provided), the formula uses the virtual
* temperature approach which accounts for the lower density of moist air compared to dry air.
* This correction typically results in slightly higher altitude estimates for the same pressure
* difference, as moist air is less dense than dry air at the same temperature and pressure.
*
* @param {number} referencePressure - Reference pressure in Pascals (Pa) at the reference altitude.
* @param {number} observedPressure - Observed pressure in Pascals (Pa) at the unknown altitude.
* @param {number} referenceAltitude - Altitude in meters (m) where the reference pressure was measured. Defaults to 0 (sea level).
* @param {number} temperature - Average temperature in Kelvin (K) between the two altitudes. Defaults to 288.15 K (15°C).
* @param {number} [relativeHumidity] - Optional relative humidity in percentage (0-100%). When provided, the calculation uses virtual temperature to account for moist air effects.
* @returns {number} The final altitude in meters (m) where the observed pressure occurs.
*
* @example
* // Calculate altitude when pressure drops from 101325 Pa (sea level) to 89874 Pa
* const altitude = altitudeFromPressureDifference(101325, 89874, 0, 288.15);
* console.log(altitude); // ~1000 m
* // Calculate altitude for dry air when pressure drops from 101325 Pa (sea level) to 89874 Pa
* const altitudeDry = altitudeFromPressureDifference(101325, 89874, 0, 288.15);
* console.log(altitudeDry); // ~1011 m
*
* @example
* // Calculate altitude for moist air (60% relative humidity)
* const altitudeMoist = altitudeFromPressureDifference(101325, 89874, 0, 288.15, 60);
* console.log(altitudeMoist); // ~1021 m (slightly higher due to lower density of moist air)
*
* @see https://en.wikipedia.org/wiki/Hypsometric_equation
* @see https://en.wikipedia.org/wiki/Virtual_temperature
*/
export function altitudeFromPressureDifference(
referencePressure: number,
observedPressure: number,
referenceAltitude: number = 0,
temperature: number = c.STANDARD_MEAN_TEMPERATURE_KELVIN
temperature: number = c.STANDARD_MEAN_TEMPERATURE_KELVIN,
relativeHumidity?: number
): number {
const g = c.DRY_AIR_CONSTANTS.gravity; // Gravitational acceleration (m/s²)
const R = c.DRY_AIR_CONSTANTS.gasConstant; // Specific gas constant for dry air (J/(kg·K))

// Determine the effective temperature to use in the calculation
let effectiveTemperature = temperature;

if (relativeHumidity !== undefined) {
// For moist air, use the virtual temperature approach
// Virtual temperature accounts for the effect of water vapor on air density
// by treating moist air as dry air at a slightly higher temperature

// Calculate average pressure for mixing ratio calculation
// Using geometric mean because pressure varies exponentially with altitude,
// so geometric mean better represents the average pressure in the layer
const avgPressure = Math.sqrt(referencePressure * observedPressure);

// Calculate saturation vapor pressure at the given temperature
const svp = saturationVaporPressure(temperature);

// Calculate actual vapor pressure from relative humidity
const avp = actualVaporPressure(svp, relativeHumidity);

// Calculate mixing ratio in g/kg
const mixRatio = calcMixingRatio(avp, avgPressure);

// Use virtual temperature which accounts for moist air
effectiveTemperature = virtualTemperature(temperature, mixRatio);
}

// Using the hypsometric formula: h = (R * T / g) * ln(P1 / P2)
// Where h is the altitude difference, R is gas constant, T is temperature,
// Where h is the altitude difference, R is gas constant, T is temperature (or virtual temperature),
// g is gravity, P1 is reference pressure, P2 is observed pressure
const altitudeDifference = (R * temperature / g) * Math.log(referencePressure / observedPressure);
const altitudeDifference = (R * effectiveTemperature / g) * Math.log(referencePressure / observedPressure);

// Return the final altitude (reference altitude + altitude difference)
return referenceAltitude + altitudeDifference;
Expand Down
62 changes: 62 additions & 0 deletions tests/formulas/altitude.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,68 @@ describe('altitudeFromPressureDifference', () => {
// Warmer air is less dense, so same pressure difference = larger altitude change
expect(warmResult).toBeGreaterThan(coldResult);
});

// New tests for moist air (relative humidity) support
it('should calculate higher altitude for moist air than dry air', () => {
// Moist air is less dense than dry air at the same temperature and pressure
// so the same pressure difference corresponds to a greater altitude change
const dryResult = altitudeFromPressureDifference(101325, 89874, 0, 288.15);
const moistResult = altitudeFromPressureDifference(101325, 89874, 0, 288.15, 60);

// Moist air result should be higher than dry air result
expect(moistResult).toBeGreaterThan(dryResult);
});

it('should show increasing altitude difference with increasing humidity', () => {
// Higher humidity means more water vapor, which is lighter than dry air
const result0 = altitudeFromPressureDifference(101325, 89874, 0, 288.15, 0); // 0% RH
const result50 = altitudeFromPressureDifference(101325, 89874, 0, 288.15, 50); // 50% RH
const result100 = altitudeFromPressureDifference(101325, 89874, 0, 288.15, 100); // 100% RH

expect(result50).toBeGreaterThan(result0);
expect(result100).toBeGreaterThan(result50);
});

it('should have minimal humidity effect at low temperatures', () => {
// At low temperatures, saturation vapor pressure is low, so humidity effect is minimal
const dryResult = altitudeFromPressureDifference(101325, 89874, 0, 263.15); // -10°C dry
const moistResult = altitudeFromPressureDifference(101325, 89874, 0, 263.15, 100); // -10°C, 100% RH

// The difference should be small (less than 1% of altitude)
const percentDiff = ((moistResult - dryResult) / dryResult) * 100;
expect(percentDiff).toBeLessThan(1);
});

it('should have larger humidity effect at high temperatures', () => {
// At high temperatures, saturation vapor pressure is high, so humidity effect is larger
const dryResult = altitudeFromPressureDifference(101325, 89874, 0, 303.15); // 30°C dry
const moistResult = altitudeFromPressureDifference(101325, 89874, 0, 303.15, 100); // 30°C, 100% RH

// The difference should be noticeable (more than 1% of altitude)
const percentDiff = ((moistResult - dryResult) / dryResult) * 100;
expect(percentDiff).toBeGreaterThan(1);
});

it('should return same result with 0% humidity as with no humidity parameter', () => {
// At 0% relative humidity, no water vapor is present, so result should match dry air
const dryResult = altitudeFromPressureDifference(101325, 89874, 0, 288.15);
const zeroHumidityResult = altitudeFromPressureDifference(101325, 89874, 0, 288.15, 0);

// Results should be very close (within 0.01%)
expect(zeroHumidityResult).toBeCloseTo(dryResult, 1);
});

it('should handle high altitude pressure differences with humidity', () => {
// Sea level pressure: 101325 Pa, pressure at ~5000m: ~54020 Pa
const dryResult = altitudeFromPressureDifference(101325, 54020, 0, 288.15);
const moistResult = altitudeFromPressureDifference(101325, 54020, 0, 288.15, 60);

// Moist air should give higher altitude
expect(moistResult).toBeGreaterThan(dryResult);
// The humidity effect at 60% RH at 15°C should add approximately 0.5% to altitude
// Dry result is ~5305 m, moist result should be ~5333 m
expect(moistResult).toBeCloseTo(5333, 0);
});
});

describe('cloudBaseHeight', () => {
Expand Down