Files
rfcp/backend/app/geometry/los.py
mytec defa3ad440 @mytec: feat: Phase 3.0 Architecture Refactor
Major refactoring of RFCP backend:
- Modular propagation models (8 models)
- SharedMemoryManager for terrain data
- ProcessPoolExecutor parallel processing
- WebSocket progress streaming
- Building filtering pipeline (351k → 15k)
- 82 unit tests

Performance: Standard preset 38s → 5s (7.6x speedup)

Known issue: Detailed preset timeout (fix in 3.1.0)
2026-02-01 23:12:26 +02:00

86 lines
2.2 KiB
Python

"""
Line-of-sight checks using terrain profile data.
"""
import math
from typing import Optional, Dict, List
EARTH_RADIUS = 6371000
K_FACTOR = 4 / 3 # Standard atmospheric refraction
def check_los_terrain(
profile: List[dict],
tx_height: float,
rx_height: float,
) -> dict:
"""
Check line-of-sight from a terrain elevation profile.
Args:
profile: List of dicts with 'elevation' and 'distance' keys.
tx_height: TX antenna height above ground (meters).
rx_height: RX height above ground (meters).
Returns:
dict with has_los, clearance, blocked_at
"""
if not profile:
return {"has_los": True, "clearance": 0.0, "blocked_at": None}
tx_ground = profile[0]["elevation"]
rx_ground = profile[-1]["elevation"]
tx_total = tx_ground + tx_height
rx_total = rx_ground + rx_height
total_distance = profile[-1]["distance"]
min_clearance = float("inf")
blocked_at = None
for point in profile:
d = point["distance"]
terrain_elev = point["elevation"]
if total_distance == 0:
los_height = tx_total
else:
los_height = tx_total + (rx_total - tx_total) * (d / total_distance)
# Earth curvature correction
effective_radius = K_FACTOR * EARTH_RADIUS
curvature = (d * (total_distance - d)) / (2 * effective_radius)
los_height_corrected = los_height - curvature
clearance = los_height_corrected - terrain_elev
if clearance < min_clearance:
min_clearance = clearance
if clearance <= 0:
blocked_at = d
return {
"has_los": min_clearance > 0,
"clearance": min_clearance,
"blocked_at": blocked_at,
}
def fresnel_radius(
d1_m: float, d2_m: float, wavelength_m: float, zone: int = 1
) -> float:
"""Calculate Fresnel zone radius at a point along the path.
Args:
d1_m: Distance from TX to point
d2_m: Distance from point to RX
wavelength_m: Signal wavelength
zone: Fresnel zone number (default 1)
Returns:
Radius in meters
"""
total = d1_m + d2_m
if total <= 0:
return 0.0
return math.sqrt(zone * wavelength_m * d1_m * d2_m / total)