added obstacles
This commit is contained in:
Binary file not shown.
@@ -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.
@@ -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}",
|
||||||
)
|
)
|
||||||
|
|||||||
Binary file not shown.
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user