Navigation

This commit is contained in:
Frank
2026-09-01 16:46:29 +02:00
parent 03c303e1a0
commit 401efa735d
3 changed files with 1350 additions and 13 deletions
File diff suppressed because it is too large Load Diff
@@ -5645,3 +5645,85 @@ function UTILS.CalculateInterceptBearing(A1, V1, A2, V2_speed)
return UTILS.Round(bearing,0)
end
--- Returns the speed of sound in dry air from the static air temperature.
-- @param #number Temperature Static air temperature in degrees Celsius.
-- @return #number Speed of sound in m/s.
-- @return #nil Invalid temperature.
function UTILS.GetSpeedOfSound( Temperature )
if type(Temperature) ~= "number" or Temperature ~= Temperature
or Temperature <= -273.15 or Temperature == math.huge then
return nil
end
return math.sqrt(1.4 * 287.05287 * (Temperature + 273.15))
end
--- Returns the ideal pitot impact-pressure ratio qc/p from Mach number.
-- qc is pitot pressure minus static pressure; p is upstream static pressure.
-- Includes the normal shock ahead of a supersonic pitot probe.
-- Assumes dry, calorically perfect air with gamma = 1.4.
-- @param #number Mach Mach number, dimensionless and non-negative.
-- @return #number Impact-pressure ratio qc/p, dimensionless.
-- @return #nil Invalid input or non-finite result.
function UTILS.MachToImpactPressureRatio( Mach )
if type(Mach) ~= "number" or Mach ~= Mach
or Mach < 0 or Mach == math.huge then
return nil
end
local M2 = Mach * Mach
local Ratio
if Mach <= 1 then
Ratio = (1 + 0.2 * M2) ^ 3.5 - 1
else
local PressureRatio = (7 * M2 - 1) / 6
local DownstreamMachSquared = (M2 + 5) / (7 * M2 - 1)
Ratio = PressureRatio
* (1 + 0.2 * DownstreamMachSquared) ^ 3.5 - 1
end
if Ratio ~= Ratio or Ratio == math.huge then return nil end
return Ratio
end
--- Returns Mach number from the ideal pitot impact-pressure ratio qc/p.
-- Supports subsonic and supersonic flow; uses bisection above Mach 1.
-- @param #number PressureRatio Impact-pressure ratio qc/p, dimensionless.
-- @return #number Mach number, dimensionless.
-- @return #nil Invalid input or failed numerical evaluation.
function UTILS.ImpactPressureRatioToMach( PressureRatio )
if type(PressureRatio) ~= "number" or PressureRatio ~= PressureRatio
or PressureRatio < 0 or PressureRatio == math.huge then
return nil
end
if PressureRatio <= UTILS.MachToImpactPressureRatio(1) then
return math.sqrt(
math.max(0, 5 * ((1 + PressureRatio) ^ (2 / 7) - 1))
)
end
local Low, High = 1, math.sqrt(PressureRatio + 1)
for Iteration = 1, 60 do
local Mid = (Low + High) / 2
local Ratio = UTILS.MachToImpactPressureRatio(Mid)
if Ratio == nil then return nil end
if Ratio < PressureRatio then
Low = Mid
else
High = Mid
end
end
return (Low + High) / 2
end
@@ -1132,6 +1132,95 @@ function POSITIONABLE:GetGroundSpeed()
return gs
end
--- Returns horizontal ground speed.
-- The vertical velocity component is excluded.
-- @param #POSITIONABLE self
-- @return #number Ground speed in m/s. Returns 0 if velocity is unavailable.
function POSITIONABLE:GetGroundSpeed()
local Velocity = self:GetVelocityVec3()
if not Velocity then return 0 end
return UTILS.Vec2Norm({x=Velocity.x, y=Velocity.z})
end
--- Returns true airspeed relative to the surrounding air.
-- Uses all three velocity components and wind without turbulence.
-- @param #POSITIONABLE self
-- @return #number TAS in m/s. Returns 0 if data is unavailable.
function POSITIONABLE:GetAirspeedTrue()
local Coordinate = self:GetCoord()
local Velocity = self:GetVelocityVec3()
if not Coordinate or not Velocity then return 0 end
local Wind = Coordinate:GetWindVec3(Coordinate.y, false)
if not Wind then return 0 end
local AirVelocity = UTILS.VecSubstract(Velocity, Wind)
return UTILS.VecNorm(AirVelocity)
end
--- Returns the Mach number using local static air temperature.
-- Uses wind without turbulence.
-- @param #POSITIONABLE self
-- @return #number Mach number, dimensionless.
-- @return #nil Required data is unavailable or invalid.
function POSITIONABLE:GetMachNumber()
local Coordinate = self:GetCoord()
local Velocity = self:GetVelocityVec3()
if not Coordinate or not Velocity then return nil end
local Wind = Coordinate:GetWindVec3(Coordinate.y, false)
local SpeedOfSound = UTILS.GetSpeedOfSound(Coordinate:GetTemperature())
if not Wind or not SpeedOfSound then return nil end
-- Read TAS here to distinguish unavailable data from a valid TAS of zero.
local AirVelocity = UTILS.VecSubstract(Velocity, Wind)
local TAS = UTILS.VecNorm(AirVelocity)
if TAS ~= TAS or TAS == math.huge then return nil end
return TAS / SpeedOfSound
end
--- Returns a CAS-based estimate of indicated airspeed.
-- Computes ideal calibrated airspeed, including supersonic pitot correction.
-- This is NOT a cockpit reading and does not model instrument or position errors.
-- Uses local static pressure, static temperature and wind without turbulence.
-- @param #POSITIONABLE self
-- @return #number Estimated IAS in m/s, numerically equal to calculated CAS.
-- @return #nil Required data is unavailable or invalid.
function POSITIONABLE:GetAirspeedIndicatedEstimated()
local Mach = self:GetMachNumber()
if Mach == nil then return nil end
local Coordinate = self:GetCoord()
if not Coordinate then return nil end
-- MOOSE returns local static pressure in hPa, not Pa.
local Pressure = Coordinate:GetPressure()
if type(Pressure) ~= "number" or Pressure ~= Pressure
or Pressure <= 0 or Pressure == math.huge then
return nil
end
local ImpactRatio = UTILS.MachToImpactPressureRatio(Mach)
if ImpactRatio == nil then return nil end
-- Convert qc/p to qc/p0 using ISA sea-level pressure p0 = 1013.25 hPa.
local ReferenceRatio = ImpactRatio * (Pressure / 1013.25)
local ReferenceMach = UTILS.ImpactPressureRatioToMach(ReferenceRatio)
if ReferenceMach == nil then return nil end
-- ISA sea-level static temperature is 15 degrees Celsius.
return ReferenceMach * UTILS.GetSpeedOfSound(15)
end
--- Returns the Angle of Attack of a POSITIONABLE.
-- @param #POSITIONABLE self
-- @return #number Angle of attack in degrees.