Files
rfcp/backend/app/services/atmospheric_service.py

99 lines
2.9 KiB
Python

import math
class AtmosphericService:
"""ITU-R P.676 atmospheric absorption model"""
# Simplified model for frequencies < 50 GHz
# Standard atmosphere: T=15C, P=1013 hPa, humidity=50%
def calculate_atmospheric_loss(
self,
frequency_mhz: float,
distance_km: float,
temperature_c: float = 15.0,
humidity_percent: float = 50.0,
altitude_m: float = 0.0,
) -> float:
"""
Calculate atmospheric absorption loss
Args:
frequency_mhz: Frequency in MHz
distance_km: Path length in km
temperature_c: Temperature in Celsius
humidity_percent: Relative humidity (0-100)
altitude_m: Altitude above sea level
Returns:
Loss in dB
"""
freq_ghz = frequency_mhz / 1000
# Below 1 GHz - negligible
if freq_ghz < 1.0:
return 0.0
# Calculate specific attenuation (dB/km)
gamma = self._specific_attenuation(freq_ghz, temperature_c, humidity_percent)
# Altitude correction (less atmosphere at higher altitudes)
altitude_factor = math.exp(-altitude_m / 8500) # Scale height ~8.5km
loss = gamma * distance_km * altitude_factor
return min(loss, 20.0) # Cap for reasonable distances
def _specific_attenuation(
self,
freq_ghz: float,
temperature_c: float,
humidity_percent: float,
) -> float:
"""
Calculate specific attenuation in dB/km
Simplified ITU-R P.676 model
"""
# Water vapor density (g/m3) - simplified
# Saturation vapor pressure (hPa)
es = 6.1121 * math.exp(
(18.678 - temperature_c / 234.5)
* (temperature_c / (257.14 + temperature_c))
)
rho = (humidity_percent / 100) * es * 216.7 / (273.15 + temperature_c)
# Oxygen absorption (dominant at 60 GHz, minor below 10 GHz)
if freq_ghz < 10:
gamma_o = 0.001 * freq_ghz ** 2 # Very small
elif freq_ghz < 57:
gamma_o = 0.001 * (freq_ghz / 10) ** 2.5
else:
# Near 60 GHz resonance
gamma_o = 15.0 # Peak absorption
# Water vapor absorption (peaks at 22 GHz and 183 GHz)
if freq_ghz < 10:
gamma_w = 0.0001 * rho * freq_ghz ** 2
elif freq_ghz < 50:
gamma_w = 0.001 * rho * (freq_ghz / 22) ** 2
else:
gamma_w = 0.01 * rho
return gamma_o + gamma_w
@staticmethod
def get_weather_description(loss_db: float) -> str:
"""Describe atmospheric conditions based on loss"""
if loss_db < 0.1:
return "clear"
elif loss_db < 0.5:
return "normal"
elif loss_db < 2.0:
return "humid"
else:
return "foggy"
atmospheric_service = AtmosphericService()