76 lines
2.3 KiB
Python
76 lines
2.3 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from typing import Literal
|
|
|
|
from app.core.geo import PathPoint
|
|
|
|
ObstructionKind = Literal["terrain", "building", "canopy"]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class SurfaceSample:
|
|
i: int
|
|
lat: float
|
|
lon: float
|
|
distance_m: float
|
|
ground_m: float
|
|
building_m: float
|
|
canopy_m: float
|
|
surface_m: float
|
|
curvature_corr_m: float = 0.0
|
|
|
|
@property
|
|
def dominant_obstruction(self) -> ObstructionKind:
|
|
if self.building_m > 0 and self.ground_m + self.building_m >= self.surface_m:
|
|
return "building"
|
|
if self.canopy_m > 0 and self.ground_m + self.canopy_m >= self.surface_m:
|
|
return "canopy"
|
|
return "terrain"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class SurfaceProfile:
|
|
distance_m: float
|
|
samples: list[SurfaceSample]
|
|
|
|
|
|
def build_surface_profile(
|
|
points: list[PathPoint],
|
|
ground_elevations: list[float] | None = None,
|
|
building_heights: list[float] | None = None,
|
|
canopy_heights: list[float] | None = None,
|
|
include_buildings: bool = True,
|
|
include_canopy: bool = True,
|
|
) -> SurfaceProfile:
|
|
if not points:
|
|
raise ValueError("points must not be empty")
|
|
|
|
count = len(points)
|
|
ground_elevations = ground_elevations or [0.0] * count
|
|
building_heights = building_heights or [0.0] * count
|
|
canopy_heights = canopy_heights or [0.0] * count
|
|
if not (len(ground_elevations) == len(building_heights) == len(canopy_heights) == count):
|
|
raise ValueError("profile arrays must match points length")
|
|
|
|
samples: list[SurfaceSample] = []
|
|
for i, point in enumerate(points):
|
|
ground_m = float(ground_elevations[i])
|
|
building_m = float(building_heights[i]) if include_buildings else 0.0
|
|
canopy_m = float(canopy_heights[i]) if include_canopy else 0.0
|
|
surface_m = ground_m + max(building_m, canopy_m)
|
|
samples.append(
|
|
SurfaceSample(
|
|
i=i,
|
|
lat=point.lat,
|
|
lon=point.lon,
|
|
distance_m=point.distance_m,
|
|
ground_m=ground_m,
|
|
building_m=building_m,
|
|
canopy_m=canopy_m,
|
|
surface_m=surface_m,
|
|
)
|
|
)
|
|
|
|
return SurfaceProfile(distance_m=points[-1].distance_m, samples=samples)
|