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

85 lines
2.5 KiB
Python

from __future__ import annotations
import numpy as np
from astropy import units as u
from pycraf import pathprof
from pyproj import Geod
from app.config import get_settings
from app.core.dem import elevations_along
from app.core.geo import GeoPoint, haversine, sample_path
_GEOD = Geod(ellps="WGS84")
def _clutter_zone(environment: str) -> pathprof.CLUTTER:
mapping = {
"urban": pathprof.CLUTTER.URBAN,
"suburban": pathprof.CLUTTER.SUBURBAN,
"rural": pathprof.CLUTTER.SPARSE,
}
return mapping.get(environment, pathprof.CLUTTER.SPARSE)
def _height_profile(
tx: GeoPoint,
rx: GeoPoint,
*,
dem_path: str | None = None,
samples: int = 64,
) -> tuple[np.ndarray, np.ndarray, float, float]:
points = sample_path(tx, rx, samples)
try:
heights = elevations_along(points, dem_path=dem_path or get_settings().dem_path)
except Exception:
heights = np.zeros(len(points), dtype=float)
dists = np.array([point.distance_m for point in points], dtype=float) * u.m
return dists, heights * u.m, points[0].lon, points[0].lat
def p452_path_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:
distance_km = haversine(tx, rx) / 1000.0
if elevation_profile_m is not None:
count = len(elevation_profile_m)
dists = np.linspace(0, distance_km * 1000, count) * u.m
heights = np.array(elevation_profile_m, dtype=float) * u.m
bearing, _, _ = _GEOD.inv(tx.lon, tx.lat, rx.lon, rx.lat)
backbearing = (bearing + 180) % 360
else:
dists, heights, _, _ = _height_profile(tx, rx, dem_path=dem_path)
bearing, _, _ = _GEOD.inv(tx.lon, tx.lat, rx.lon, rx.lat)
backbearing = (bearing + 180) % 360
pathprop = pathprof.PathProp(
freq_mhz * u.MHz,
293 * u.K,
1013 * u.hPa,
tx.lon * u.deg,
tx.lat * u.deg,
rx.lon * u.deg,
rx.lat * u.deg,
tx_height_agl * u.m,
rx_height_agl * u.m,
max(100.0, distance_km * 10) * u.m,
50 * u.percent,
zone_t=_clutter_zone(environment),
zone_r=_clutter_zone(environment),
hprof_dists=dists,
hprof_heights=heights,
hprof_bearing=bearing * u.deg,
hprof_backbearing=backbearing * u.deg,
)
losses = pathprof.loss_complete(pathprop)
total = losses[0]
return float(total.value)