99 lines
2.9 KiB
Python
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()
|