Files
RadioPropagationApi/api/app/core/propagation.py
T
2026-06-24 07:50:00 +03:00

131 lines
3.3 KiB
Python

from __future__ import annotations
from dataclasses import dataclass
from math import log10
from app.core.geo import GeoPoint, haversine
from app.core.itm import itm_path_loss, p1812_path_loss
from app.core.p452 import p452_path_loss
@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(
tx: GeoPoint,
rx: GeoPoint,
*,
tx_height_agl: float,
rx_height_agl: float,
freq_mhz: float,
elevation_profile_m: list[float] | None = None,
dem_path: str | None = None,
) -> float:
return itm_path_loss(
tx,
rx,
tx_height_agl=tx_height_agl,
rx_height_agl=rx_height_agl,
freq_mhz=freq_mhz,
elevation_profile_m=elevation_profile_m,
dem_path=dem_path,
)
def p1812_field(
tx: GeoPoint,
rx: GeoPoint,
*,
tx_height_agl: float,
rx_height_agl: float,
freq_mhz: float,
environment: str = "rural",
elevation_profile_m: list[float] | None = None,
dem_path: str | None = None,
) -> float:
return p1812_path_loss(
tx,
rx,
tx_height_agl=tx_height_agl,
rx_height_agl=rx_height_agl,
freq_mhz=freq_mhz,
environment=environment,
elevation_profile_m=elevation_profile_m,
dem_path=dem_path,
)
def p452_loss(
tx: GeoPoint,
rx: GeoPoint,
*,
tx_height_agl: float,
rx_height_agl: float,
freq_mhz: float,
environment: str = "rural",
elevation_profile_m: list[float] | None = None,
dem_path: str | None = None,
) -> float:
return p452_path_loss(
tx,
rx,
tx_height_agl=tx_height_agl,
rx_height_agl=rx_height_agl,
freq_mhz=freq_mhz,
environment=environment,
elevation_profile_m=elevation_profile_m,
dem_path=dem_path,
)
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,
)