Files
RadioPropagationApi/api/app/core/propagation.py
T
2026-06-23 09:13:51 +03:00

70 lines
2.0 KiB
Python

from __future__ import annotations
from dataclasses import dataclass
from math import log10
from app.core.geo import GeoPoint, haversine
@dataclass(frozen=True)
class LinkBudget:
distance_km: float
fspl_db: float
diffraction_db: float
vegetation_db: float
atmospheric_db: float
total_loss_db: float
rx_power_dbm: float
fade_margin_db: float
fresnel_clear: bool
link_viable: bool
def fspl(freq_mhz: float, dist_km: float) -> float:
if freq_mhz <= 0:
raise ValueError("freq_mhz must be positive")
if dist_km <= 0:
raise ValueError("dist_km must be positive")
return 32.44 + 20 * log10(freq_mhz) + 20 * log10(dist_km)
def itm_loss(*args: object, **kwargs: object) -> float:
raise NotImplementedError("Longley-Rice ITM integration is implemented in a later stage")
def p1812_field(*args: object, **kwargs: object) -> float:
raise NotImplementedError("ITU-R P.1812 integration is implemented in a later stage")
def manual_link_budget(
tx: GeoPoint,
rx: GeoPoint,
tx_power_dbm: float,
tx_gain_dbi: float,
rx_gain_dbi: float,
sensitivity_dbm: float,
frequency_mhz: float,
diffraction_db: float = 0.0,
vegetation_db: float = 0.0,
atmospheric_db: float = 0.0,
misc_loss_db: float = 0.0,
fresnel_clear: bool = True,
) -> LinkBudget:
distance_km = haversine(tx, rx) / 1000.0
free_space_loss = fspl(frequency_mhz, distance_km)
total_loss = free_space_loss + diffraction_db + vegetation_db + atmospheric_db + misc_loss_db
rx_power = tx_power_dbm + tx_gain_dbi + rx_gain_dbi - total_loss
margin = rx_power - sensitivity_dbm
return LinkBudget(
distance_km=distance_km,
fspl_db=free_space_loss,
diffraction_db=diffraction_db,
vegetation_db=vegetation_db,
atmospheric_db=atmospheric_db,
total_loss_db=total_loss,
rx_power_dbm=rx_power,
fade_margin_db=margin,
fresnel_clear=fresnel_clear,
link_viable=margin > 0 and fresnel_clear,
)