added obstacles

This commit is contained in:
2026-06-23 11:49:32 +03:00
parent 69b810450e
commit 6be9810404
6 changed files with 48 additions and 2 deletions
Binary file not shown.
+6
View File
@@ -51,6 +51,8 @@ class LosRequest(BaseModel):
class Obstruction(BaseModel): class Obstruction(BaseModel):
distance_m: float distance_m: float
clearance_m: float clearance_m: float
fresnel_radius_m: float
required_clearance_m: float
type: Literal["terrain", "building", "canopy"] type: Literal["terrain", "building", "canopy"]
@@ -60,5 +62,9 @@ class LosResponse(BaseModel):
first_fresnel_clearance_pct: float first_fresnel_clearance_pct: float
worst_obstruction: Obstruction | None worst_obstruction: Obstruction | None
obstructions: list[Obstruction] obstructions: list[Obstruction]
geometric_obstructions: list[Obstruction]
fresnel_violations_count: int
geometric_obstructions_count: int
building_obstructions_count: int
diffraction_loss_db: float diffraction_loss_db: float
profile_ref: str profile_ref: str
Binary file not shown.
+17 -2
View File
@@ -7,7 +7,7 @@ from sqlalchemy.orm import Session
from app.config import get_settings from app.config import get_settings
from app.core import dem from app.core import dem
from app.core.diffraction import deygout from app.core.diffraction import deygout
from app.core.fresnel import los_analysis from app.core.fresnel import LosSample, los_analysis
from app.core.geo import GeoPoint, PathPoint, linestring_geojson, sample_path from app.core.geo import GeoPoint, PathPoint, linestring_geojson, sample_path
from app.core.surface import build_surface_profile from app.core.surface import build_surface_profile
from app.models.terrain import ( from app.models.terrain import (
@@ -102,19 +102,34 @@ def los(
request.frequency_mhz * 1_000_000, request.frequency_mhz * 1_000_000,
) )
def convert(sample: object) -> Obstruction: def convert(sample: LosSample) -> Obstruction:
return Obstruction( return Obstruction(
distance_m=sample.distance_m, distance_m=sample.distance_m,
clearance_m=sample.clearance_m, clearance_m=sample.clearance_m,
fresnel_radius_m=sample.fresnel_radius_m,
required_clearance_m=sample.required_clearance_m,
type=sample.obstruction_type, type=sample.obstruction_type,
) )
geometric_obstructions = [
sample for sample in result.obstructions if sample.clearance_m < 0
]
building_obstructions = [
sample
for sample in geometric_obstructions
if sample.obstruction_type == "building"
]
return LosResponse( return LosResponse(
los_clear=result.los_clear, los_clear=result.los_clear,
geometric_los=result.geometric_los, geometric_los=result.geometric_los,
first_fresnel_clearance_pct=result.first_fresnel_clearance_pct, first_fresnel_clearance_pct=result.first_fresnel_clearance_pct,
worst_obstruction=convert(result.worst_sample) if result.worst_sample else None, worst_obstruction=convert(result.worst_sample) if result.worst_sample else None,
obstructions=[convert(sample) for sample in result.obstructions], obstructions=[convert(sample) for sample in result.obstructions],
geometric_obstructions=[convert(sample) for sample in geometric_obstructions],
fresnel_violations_count=len(result.obstructions),
geometric_obstructions_count=len(geometric_obstructions),
building_obstructions_count=len(building_obstructions),
diffraction_loss_db=diffraction_loss, diffraction_loss_db=diffraction_loss,
profile_ref=f"inline:{profile_request.samples}", profile_ref=f"inline:{profile_request.samples}",
) )
+25
View File
@@ -45,6 +45,31 @@ def test_elevation_returns_not_implemented_without_dem() -> None:
assert response.status_code == 501 assert response.status_code == 501
def test_los_endpoint_returns_obstruction_summary_fields() -> None:
response = client.post(
"/api/v1/terrain/los",
json={
"tx": {"lat": 59.935, "lon": 30.305, "height_agl": 30},
"rx": {"lat": 59.945, "lon": 30.325, "height_agl": 2},
"frequency_mhz": 433,
"fresnel_clearance": 0.6,
"include_buildings": False,
"include_canopy": False,
"k_factor": 1.333,
"samples": 16,
},
)
assert response.status_code == 200
data = response.json()
assert "obstructions" in data
assert "geometric_obstructions" in data
assert data["fresnel_violations_count"] == len(data["obstructions"])
assert data["geometric_obstructions_count"] == len(data["geometric_obstructions"])
assert data["building_obstructions_count"] == len(
[item for item in data["geometric_obstructions"] if item["type"] == "building"]
)
def test_manual_link_budget_endpoint() -> None: def test_manual_link_budget_endpoint() -> None:
response = client.post( response = client.post(
"/api/v1/link/budget", "/api/v1/link/budget",