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

52 lines
1.7 KiB
Python

from __future__ import annotations
from dataclasses import dataclass
from math import exp
@dataclass(frozen=True)
class AntennaPattern:
pattern: str = "omni"
azimuth_deg: float = 0.0
tilt_deg: float = 0.0
gain_dbi: float = 0.0
beamwidth_h: float | None = None
beamwidth_v: float | None = None
front_to_back_db: float = 25.0
sidelobe_floor_db: float = -30.0
def _angular_delta(angle: float, center: float) -> float:
return (angle - center + 180.0) % 360.0 - 180.0
def _gaussian_drop(delta: float, beamwidth: float) -> float:
if beamwidth <= 0:
raise ValueError("beamwidth must be positive")
sigma = beamwidth / 2.355
normalized = exp(-0.5 * (delta / sigma) ** 2)
if normalized <= 0:
return float("-inf")
return 10.0 * (normalized - 1.0)
def gain(pattern: AntennaPattern, az_deg: float, el_deg: float) -> float:
if pattern.pattern == "omni":
return pattern.gain_dbi
if pattern.pattern == "file":
raise NotImplementedError("Antenna pattern file parsing is implemented in a later stage")
if pattern.pattern != "sector":
raise ValueError(f"Unknown antenna pattern: {pattern.pattern}")
if pattern.beamwidth_h is None or pattern.beamwidth_v is None:
raise ValueError("sector pattern requires horizontal and vertical beamwidths")
az_delta = _angular_delta(az_deg, pattern.azimuth_deg)
el_delta = el_deg - pattern.tilt_deg
h_drop = _gaussian_drop(az_delta, pattern.beamwidth_h)
v_drop = _gaussian_drop(el_delta, pattern.beamwidth_v)
attenuation = max(pattern.sidelobe_floor_db, h_drop + v_drop)
if abs(az_delta) > 90:
attenuation = min(attenuation, -pattern.front_to_back_db)
return pattern.gain_dbi + attenuation