134 lines
3.3 KiB
Python
134 lines
3.3 KiB
Python
from __future__ import annotations
|
|
|
|
import math
|
|
|
|
import numpy as np
|
|
from itmlogic.misc.qerfi import qerfi
|
|
from itmlogic.preparatory_subroutines.qlrpfl import qlrpfl
|
|
from itmlogic.statistics.avar import avar
|
|
|
|
from app.config import get_settings
|
|
from app.core.dem import elevations_along
|
|
from app.core.geo import GeoPoint, haversine, sample_path
|
|
|
|
_DB = 8.685890
|
|
|
|
|
|
def _climate_code(environment: str) -> int:
|
|
mapping = {
|
|
"urban": 5,
|
|
"suburban": 6,
|
|
"rural": 7,
|
|
}
|
|
return mapping.get(environment, 7)
|
|
|
|
|
|
def _build_prop(
|
|
*,
|
|
freq_mhz: float,
|
|
distance_km: float,
|
|
tx_height_m: float,
|
|
rx_height_m: float,
|
|
surface_profile_m: list[float],
|
|
climate: int,
|
|
) -> dict:
|
|
prop: dict = {
|
|
"fmhz": freq_mhz,
|
|
"d": distance_km,
|
|
"hg": [tx_height_m, rx_height_m],
|
|
"ipol": 0,
|
|
"eps": 15,
|
|
"sgm": 0.005,
|
|
"klim": climate,
|
|
"ens0": 314,
|
|
"lvar": 5,
|
|
"gma": 157e-9,
|
|
"kwx": 0,
|
|
"wn": freq_mhz / 47.7,
|
|
"ens": 314,
|
|
"mdvarx": 11,
|
|
"klimx": 0,
|
|
}
|
|
|
|
pfl = [len(surface_profile_m) - 1, 0, *surface_profile_m]
|
|
if pfl[0] > 0:
|
|
pfl[1] = distance_km * 1000 / pfl[0]
|
|
prop["pfl"] = pfl
|
|
prop["gme"] = prop["gma"] * (1 - 0.04665 * math.exp(prop["ens"] / 179.3))
|
|
zq = complex(prop["eps"], 376.62 * prop["sgm"] / prop["wn"])
|
|
prop["zgnd"] = np.sqrt(zq - 1)
|
|
prop = qlrpfl(prop)
|
|
return prop
|
|
|
|
|
|
def itm_path_loss(
|
|
tx: GeoPoint,
|
|
rx: GeoPoint,
|
|
*,
|
|
tx_height_agl: float,
|
|
rx_height_agl: float,
|
|
freq_mhz: float,
|
|
elevation_profile_m: list[float] | None = None,
|
|
dem_path: str | None = None,
|
|
climate: int = 5,
|
|
) -> float:
|
|
distance_km = haversine(tx, rx) / 1000.0
|
|
if elevation_profile_m is None:
|
|
points = sample_path(tx, rx, 64)
|
|
try:
|
|
elevation_profile_m = elevations_along(
|
|
points,
|
|
dem_path=dem_path or get_settings().dem_path,
|
|
).tolist()
|
|
except Exception:
|
|
elevation_profile_m = [0.0, 0.0]
|
|
|
|
profile = [float(value) for value in elevation_profile_m]
|
|
if profile:
|
|
profile[0] = profile[0] + tx_height_agl
|
|
profile[-1] = profile[-1] + rx_height_agl
|
|
|
|
prop = _build_prop(
|
|
freq_mhz=freq_mhz,
|
|
distance_km=distance_km,
|
|
tx_height_m=profile[0] if profile else tx_height_agl,
|
|
rx_height_m=profile[-1] if profile else rx_height_agl,
|
|
surface_profile_m=profile,
|
|
climate=climate,
|
|
)
|
|
fs = _DB * math.log(2 * prop["wn"] * prop["dist"])
|
|
zr = qerfi([0.5])
|
|
zc = qerfi([0.5])
|
|
correction, _ = avar(zr[0], 0, zc[0], prop)
|
|
return float(fs + correction)
|
|
|
|
|
|
def p1812_path_loss(
|
|
tx: GeoPoint,
|
|
rx: GeoPoint,
|
|
*,
|
|
tx_height_agl: float,
|
|
rx_height_agl: float,
|
|
freq_mhz: float,
|
|
environment: str,
|
|
elevation_profile_m: list[float] | None = None,
|
|
dem_path: str | None = None,
|
|
) -> float:
|
|
climate = _climate_code(environment)
|
|
base_loss = itm_path_loss(
|
|
tx,
|
|
rx,
|
|
tx_height_agl=tx_height_agl,
|
|
rx_height_agl=rx_height_agl,
|
|
freq_mhz=freq_mhz,
|
|
elevation_profile_m=elevation_profile_m,
|
|
dem_path=dem_path,
|
|
climate=climate,
|
|
)
|
|
clutter_db = {
|
|
"urban": 8.0,
|
|
"suburban": 4.0,
|
|
"rural": 0.0,
|
|
}.get(environment, 0.0)
|
|
return base_loss + clutter_db
|