85 lines
2.5 KiB
Python
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)
|