Files
RadioPropagationApi/api/app/core/diffraction.py
T
2026-06-23 09:41:06 +03:00

62 lines
1.8 KiB
Python

from __future__ import annotations
from math import log10, sqrt
from app.core.fresnel import wavelength
from app.core.surface import SurfaceProfile
def knife_edge_loss(v: float) -> float:
if v <= -0.78:
return 0.0
return 6.9 + 20 * log10(sqrt((v - 0.1) ** 2 + 1) + v - 0.1)
def knife_edge_v(h: float, d1: float, d2: float, freq_hz: float) -> float:
if d1 <= 0 or d2 <= 0:
return float("-inf")
lmbda = wavelength(freq_hz)
return h * sqrt(2 * (d1 + d2) / (lmbda * d1 * d2))
def bullington_loss(
profile: SurfaceProfile,
tx_height_agl: float,
rx_height_agl: float,
freq_hz: float,
) -> float:
"""Bullington-style equivalent edge loss for a terrain profile.
The current implementation uses the dominant obstacle relative to the TX-RX
chord as the equivalent Bullington edge, then applies ITU-R P.526 J(v).
"""
if len(profile.samples) < 3:
return 0.0
total_distance = profile.distance_m
if total_distance <= 0:
return 0.0
tx_elevation = profile.samples[0].ground_m + tx_height_agl
rx_elevation = profile.samples[-1].ground_m + rx_height_agl
max_v = float("-inf")
for sample in profile.samples[1:-1]:
d1 = sample.distance_m
d2 = total_distance - d1
path_height = tx_elevation + (rx_elevation - tx_elevation) * (d1 / total_distance)
h = sample.surface_m - path_height
max_v = max(max_v, knife_edge_v(h, d1, d2, freq_hz))
return knife_edge_loss(max_v)
def deygout(
profile: SurfaceProfile,
tx_height_agl: float,
rx_height_agl: float,
freq_hz: float,
) -> float:
"""Compatibility wrapper; use Bullington equivalent loss for multi-edge profiles."""
return bullington_loss(profile, tx_height_agl, rx_height_agl, freq_hz)