added vegetation

This commit is contained in:
2026-06-23 15:31:40 +03:00
parent c7435955c3
commit b230f7277c
9 changed files with 101 additions and 2 deletions
Binary file not shown.
+22 -2
View File
@@ -11,7 +11,12 @@ EARTH_RADIUS_M = 6_371_000.0
@dataclass(frozen=True)
class LosSample:
lat: float
lon: float
distance_m: float
surface_m: float
path_height_m: float
obstacle_height_m: float
clearance_m: float
fresnel_radius_m: float
required_clearance_m: float
@@ -25,6 +30,7 @@ class LosResult:
first_fresnel_clearance_pct: float
worst_sample: LosSample | None
obstructions: list[LosSample]
samples: list[LosSample]
def wavelength(freq_hz: float) -> float:
@@ -66,14 +72,19 @@ def los_analysis(
tx_elevation = profile.samples[0].ground_m + tx_height_agl
rx_elevation = profile.samples[-1].ground_m + rx_height_agl
obstructions: list[LosSample] = []
samples: list[LosSample] = []
worst_sample: LosSample | None = None
min_ratio = float("inf")
geometric_los = True
for sample in profile.samples[1:-1]:
for sample in profile.samples:
d1 = sample.distance_m
d2 = total_distance - d1
path_height = tx_elevation + (rx_elevation - tx_elevation) * (d1 / total_distance)
path_height = (
tx_elevation
if total_distance == 0
else tx_elevation + (rx_elevation - tx_elevation) * (d1 / total_distance)
)
obstacle_height = sample.surface_m + earth_bulge(d1, d2, k)
clearance_m = path_height - obstacle_height
f1 = fresnel_radius(lmbda, d1, d2)
@@ -82,12 +93,20 @@ def los_analysis(
obstruction_type = sample.dominant_obstruction
los_sample = LosSample(
lat=sample.lat,
lon=sample.lon,
distance_m=d1,
surface_m=sample.surface_m,
path_height_m=path_height,
obstacle_height_m=obstacle_height,
clearance_m=clearance_m,
fresnel_radius_m=f1,
required_clearance_m=required,
obstruction_type=obstruction_type,
)
samples.append(los_sample)
if d1 == 0 or d1 == total_distance:
continue
if worst_sample is None or clearance_m - required < (
worst_sample.clearance_m - worst_sample.required_clearance_m
):
@@ -104,4 +123,5 @@ def los_analysis(
first_fresnel_clearance_pct=100.0 if min_ratio == float("inf") else min_ratio,
worst_sample=worst_sample,
obstructions=obstructions,
samples=samples,
)
Binary file not shown.
+17
View File
@@ -59,6 +59,22 @@ class Obstruction(BaseModel):
type: Literal["terrain", "building", "canopy"]
class FresnelProfileSample(BaseModel):
i: int
lat: float
lon: float
distance_m: float
surface_m: float
path_height_m: float
obstacle_height_m: float
clearance_m: float
fresnel_radius_m: float
required_clearance_m: float
fresnel_lower_60pct_m: float
fresnel_lower_100pct_m: float
type: Literal["terrain", "building", "canopy"]
class LosResponse(BaseModel):
los_clear: bool
geometric_los: bool
@@ -66,6 +82,7 @@ class LosResponse(BaseModel):
worst_obstruction: Obstruction | None
obstructions: list[Obstruction]
geometric_obstructions: list[Obstruction]
fresnel_profile: list[FresnelProfileSample]
fresnel_violations_count: int
geometric_obstructions_count: int
building_obstructions_count: int
Binary file not shown.
+21
View File
@@ -14,6 +14,7 @@ from app.core.surface import SurfaceProfile, build_surface_profile
from app.models.common import DataSources
from app.models.terrain import (
ElevationResponse,
FresnelProfileSample,
LosRequest,
LosResponse,
Obstruction,
@@ -117,6 +118,23 @@ def los(
type=sample.obstruction_type,
)
def convert_fresnel_sample(i: int, sample: LosSample) -> FresnelProfileSample:
return FresnelProfileSample(
i=i,
lat=sample.lat,
lon=sample.lon,
distance_m=sample.distance_m,
surface_m=sample.surface_m,
path_height_m=sample.path_height_m,
obstacle_height_m=sample.obstacle_height_m,
clearance_m=sample.clearance_m,
fresnel_radius_m=sample.fresnel_radius_m,
required_clearance_m=sample.required_clearance_m,
fresnel_lower_60pct_m=sample.path_height_m - sample.required_clearance_m,
fresnel_lower_100pct_m=sample.path_height_m - sample.fresnel_radius_m,
type=sample.obstruction_type,
)
geometric_obstructions = [
sample for sample in result.obstructions if sample.clearance_m < 0
]
@@ -133,6 +151,9 @@ def los(
worst_obstruction=convert(result.worst_sample) if result.worst_sample else None,
obstructions=[convert(sample) for sample in result.obstructions],
geometric_obstructions=[convert(sample) for sample in geometric_obstructions],
fresnel_profile=[
convert_fresnel_sample(i, sample) for i, sample in enumerate(result.samples)
],
fresnel_violations_count=len(result.obstructions),
geometric_obstructions_count=len(geometric_obstructions),
building_obstructions_count=len(building_obstructions),