mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
231 lines
5.9 KiB
Python
231 lines
5.9 KiB
Python
from __future__ import annotations
|
|
|
|
import itertools
|
|
import math
|
|
from collections import Iterable
|
|
from dataclasses import dataclass
|
|
from typing import Union, Any, TypeVar
|
|
|
|
METERS_TO_FEET = 3.28084
|
|
FEET_TO_METERS = 1 / METERS_TO_FEET
|
|
NM_TO_METERS = 1852
|
|
METERS_TO_NM = 1 / NM_TO_METERS
|
|
|
|
KNOTS_TO_KPH = 1.852
|
|
KPH_TO_KNOTS = 1 / KNOTS_TO_KPH
|
|
MS_TO_KPH = 3.6
|
|
KPH_TO_MS = 1 / MS_TO_KPH
|
|
|
|
INHG_TO_HPA = 33.86389
|
|
INHG_TO_MMHG = 25.400002776728
|
|
|
|
|
|
def heading_sum(h: int, a: int) -> int:
|
|
h += a
|
|
return h % 360
|
|
|
|
|
|
def opposite_heading(h: int) -> int:
|
|
return heading_sum(h, 180)
|
|
|
|
|
|
@dataclass(frozen=True, order=True)
|
|
class Distance:
|
|
distance_in_meters: float
|
|
|
|
@property
|
|
def feet(self) -> float:
|
|
return self.distance_in_meters * METERS_TO_FEET
|
|
|
|
@property
|
|
def meters(self) -> float:
|
|
return self.distance_in_meters
|
|
|
|
@property
|
|
def nautical_miles(self) -> float:
|
|
return self.distance_in_meters * METERS_TO_NM
|
|
|
|
@classmethod
|
|
def from_feet(cls, value: float) -> Distance:
|
|
return cls(value * FEET_TO_METERS)
|
|
|
|
@classmethod
|
|
def from_meters(cls, value: float) -> Distance:
|
|
return cls(value)
|
|
|
|
@classmethod
|
|
def from_nautical_miles(cls, value: float) -> Distance:
|
|
return cls(value * NM_TO_METERS)
|
|
|
|
@classmethod
|
|
def inf(cls) -> Distance:
|
|
return cls.from_meters(math.inf)
|
|
|
|
def __add__(self, other: Distance) -> Distance:
|
|
return meters(self.meters + other.meters)
|
|
|
|
def __sub__(self, other: Distance) -> Distance:
|
|
return meters(self.meters - other.meters)
|
|
|
|
def __mul__(self, other: Union[float, int]) -> Distance:
|
|
return meters(self.meters * other)
|
|
|
|
def __truediv__(self, other: Union[float, int]) -> Distance:
|
|
return meters(self.meters / other)
|
|
|
|
def __floordiv__(self, other: Union[float, int]) -> Distance:
|
|
return meters(self.meters // other)
|
|
|
|
def __bool__(self) -> bool:
|
|
return not math.isclose(self.meters, 0.0)
|
|
|
|
|
|
def feet(value: float) -> Distance:
|
|
return Distance.from_feet(value)
|
|
|
|
|
|
def meters(value: float) -> Distance:
|
|
return Distance.from_meters(value)
|
|
|
|
|
|
def nautical_miles(value: float) -> Distance:
|
|
return Distance.from_nautical_miles(value)
|
|
|
|
|
|
@dataclass(frozen=True, order=True)
|
|
class Speed:
|
|
speed_in_kph: float
|
|
|
|
@property
|
|
def knots(self) -> float:
|
|
return self.speed_in_kph * KPH_TO_KNOTS
|
|
|
|
@property
|
|
def kph(self) -> float:
|
|
return self.speed_in_kph
|
|
|
|
@property
|
|
def meters_per_second(self) -> float:
|
|
return self.speed_in_kph * KPH_TO_MS
|
|
|
|
def mach(self, altitude: Distance = meters(0)) -> float:
|
|
c_sound = mach(1, altitude)
|
|
return self.speed_in_kph / c_sound.kph
|
|
|
|
@classmethod
|
|
def from_knots(cls, value: float) -> Speed:
|
|
return cls(value * KNOTS_TO_KPH)
|
|
|
|
@classmethod
|
|
def from_kph(cls, value: float) -> Speed:
|
|
return cls(value)
|
|
|
|
@classmethod
|
|
def from_meters_per_second(cls, value: float) -> Speed:
|
|
return cls(value * MS_TO_KPH)
|
|
|
|
@classmethod
|
|
def from_mach(cls, value: float, altitude: Distance) -> Speed:
|
|
# https://www.grc.nasa.gov/WWW/K-12/airplane/atmos.html
|
|
if altitude <= feet(36152):
|
|
temperature_f = 59 - 0.00356 * altitude.feet
|
|
else:
|
|
# There's another formula for altitudes over 82k feet, but we better
|
|
# not be planning waypoints that high...
|
|
temperature_f = -70
|
|
|
|
temperature_k = (temperature_f + 459.67) * (5 / 9)
|
|
|
|
# https://www.engineeringtoolbox.com/specific-heat-ratio-d_602.html
|
|
# Dependent on temperature, but varies very little (+/-0.001)
|
|
# between -40F and 180F.
|
|
heat_capacity_ratio = 1.4
|
|
|
|
# https://www.grc.nasa.gov/WWW/K-12/airplane/sound.html
|
|
gas_constant = 286 # m^2/s^2/K
|
|
c_sound = math.sqrt(heat_capacity_ratio * gas_constant * temperature_k)
|
|
return mps(c_sound) * value
|
|
|
|
def __add__(self, other: Speed) -> Speed:
|
|
return kph(self.kph + other.kph)
|
|
|
|
def __sub__(self, other: Speed) -> Speed:
|
|
return kph(self.kph - other.kph)
|
|
|
|
def __mul__(self, other: Union[float, int]) -> Speed:
|
|
return kph(self.kph * other)
|
|
|
|
def __truediv__(self, other: Union[float, int]) -> Speed:
|
|
return kph(self.kph / other)
|
|
|
|
def __floordiv__(self, other: Union[float, int]) -> Speed:
|
|
return kph(self.kph // other)
|
|
|
|
def __bool__(self) -> bool:
|
|
return not math.isclose(self.kph, 0.0)
|
|
|
|
|
|
def knots(value: float) -> Speed:
|
|
return Speed.from_knots(value)
|
|
|
|
|
|
def kph(value: float) -> Speed:
|
|
return Speed.from_kph(value)
|
|
|
|
|
|
def mps(value: float) -> Speed:
|
|
return Speed.from_meters_per_second(value)
|
|
|
|
|
|
def mach(value: float, altitude: Distance) -> Speed:
|
|
return Speed.from_mach(value, altitude)
|
|
|
|
|
|
SPEED_OF_SOUND_AT_SEA_LEVEL = knots(661.5)
|
|
|
|
|
|
@dataclass(frozen=True, order=True)
|
|
class Pressure:
|
|
pressure_in_inches_hg: float
|
|
|
|
@property
|
|
def inches_hg(self) -> float:
|
|
return self.pressure_in_inches_hg
|
|
|
|
@property
|
|
def mm_hg(self) -> float:
|
|
return self.pressure_in_inches_hg * INHG_TO_MMHG
|
|
|
|
@property
|
|
def hecto_pascals(self) -> float:
|
|
return self.pressure_in_inches_hg * INHG_TO_HPA
|
|
|
|
|
|
def inches_hg(value: float) -> Pressure:
|
|
return Pressure(value)
|
|
|
|
|
|
PairwiseT = TypeVar("PairwiseT")
|
|
|
|
|
|
def pairwise(iterable: Iterable[PairwiseT]) -> Iterable[tuple[PairwiseT, PairwiseT]]:
|
|
"""
|
|
itertools recipe
|
|
s -> (s0,s1), (s1,s2), (s2, s3), ...
|
|
"""
|
|
a, b = itertools.tee(iterable)
|
|
next(b, None)
|
|
return zip(a, b)
|
|
|
|
|
|
def interpolate(value1: float, value2: float, factor: float, clamp: bool) -> float:
|
|
"""Inerpolate between two values, factor 0-1"""
|
|
interpolated = value1 + (value2 - value1) * factor
|
|
|
|
if clamp:
|
|
bigger_value = max(value1, value2)
|
|
smaller_value = min(value1, value2)
|
|
return min(bigger_value, max(smaller_value, interpolated))
|
|
else:
|
|
return interpolated
|