added vegetation
This commit is contained in:
@@ -268,6 +268,13 @@ curl -s -X POST http://localhost:5603/api/v1/link/budget \
|
|||||||
- `fade_margin_db`
|
- `fade_margin_db`
|
||||||
- `fresnel_clear`
|
- `fresnel_clear`
|
||||||
- `link_viable`
|
- `link_viable`
|
||||||
|
- `los_clear`
|
||||||
|
- `geometric_los`
|
||||||
|
- `first_fresnel_clearance_pct`
|
||||||
|
- `fresnel_violations_count`
|
||||||
|
- `geometric_obstructions_count`
|
||||||
|
- `building_obstructions_count`
|
||||||
|
- `worst_obstruction`
|
||||||
|
|
||||||
При `include_vegetation=true` API сэмплит WorldCover вдоль трассы и добавляет
|
При `include_vegetation=true` API сэмплит WorldCover вдоль трассы и добавляет
|
||||||
P.833 vegetation attenuation в `vegetation_db`. Если landcover raster отсутствует,
|
P.833 vegetation attenuation в `vegetation_db`. Если landcover raster отсутствует,
|
||||||
@@ -275,7 +282,9 @@ P.833 vegetation attenuation в `vegetation_db`. Если landcover raster от
|
|||||||
|
|
||||||
При `include_buildings=true` API также строит terrain surface profile с DEM и
|
При `include_buildings=true` API также строит terrain surface profile с DEM и
|
||||||
OSM buildings, считает Fresnel/LOS и добавляет Bullington-style diffraction loss
|
OSM buildings, считает Fresnel/LOS и добавляет Bullington-style diffraction loss
|
||||||
в `diffraction_db`. Поле `fresnel_clear` берётся из этого же анализа.
|
в `diffraction_db`. Поле `fresnel_clear` берётся из этого же анализа. Диагностические
|
||||||
|
поля `geometric_los`, `worst_obstruction` и счётчики obstruction помогают понять,
|
||||||
|
почему `link_viable=false`.
|
||||||
|
|
||||||
Модели `p452` и `itm` пока возвращают `501 Not Implemented`.
|
Модели `p452` и `itm` пока возвращают `501 Not Implemented`.
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -3,6 +3,7 @@ from typing import Literal
|
|||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
from app.models.coverage import LinkRx, LinkTx
|
from app.models.coverage import LinkRx, LinkTx
|
||||||
|
from app.models.terrain import Obstruction
|
||||||
|
|
||||||
|
|
||||||
class LinkBudgetRequest(BaseModel):
|
class LinkBudgetRequest(BaseModel):
|
||||||
@@ -26,3 +27,10 @@ class LinkBudgetResponse(BaseModel):
|
|||||||
fade_margin_db: float
|
fade_margin_db: float
|
||||||
fresnel_clear: bool
|
fresnel_clear: bool
|
||||||
link_viable: bool
|
link_viable: bool
|
||||||
|
los_clear: bool
|
||||||
|
geometric_los: bool
|
||||||
|
first_fresnel_clearance_pct: float
|
||||||
|
fresnel_violations_count: int
|
||||||
|
geometric_obstructions_count: int
|
||||||
|
building_obstructions_count: int
|
||||||
|
worst_obstruction: Obstruction | None
|
||||||
|
|||||||
Binary file not shown.
@@ -1,10 +1,11 @@
|
|||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
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, sample_path
|
from app.core.geo import GeoPoint, sample_path
|
||||||
from app.core.propagation import manual_link_budget
|
from app.core.propagation import manual_link_budget
|
||||||
from app.models.link import LinkBudgetRequest, LinkBudgetResponse
|
from app.models.link import LinkBudgetRequest, LinkBudgetResponse
|
||||||
|
from app.models.terrain import Obstruction
|
||||||
from app.services.terrain import surface_profile_from_points
|
from app.services.terrain import surface_profile_from_points
|
||||||
from app.services.vegetation import vegetation_loss_along
|
from app.services.vegetation import vegetation_loss_along
|
||||||
|
|
||||||
@@ -53,4 +54,33 @@ def link_budget(request: LinkBudgetRequest, db: Session | None = None) -> LinkBu
|
|||||||
vegetation_db=vegetation_db,
|
vegetation_db=vegetation_db,
|
||||||
fresnel_clear=los_result.los_clear,
|
fresnel_clear=los_result.los_clear,
|
||||||
)
|
)
|
||||||
return LinkBudgetResponse(**budget.__dict__)
|
geometric_obstructions = [
|
||||||
|
sample for sample in los_result.obstructions if sample.clearance_m < 0
|
||||||
|
]
|
||||||
|
building_obstructions = [
|
||||||
|
sample
|
||||||
|
for sample in geometric_obstructions
|
||||||
|
if sample.obstruction_type == "building"
|
||||||
|
]
|
||||||
|
return LinkBudgetResponse(
|
||||||
|
**budget.__dict__,
|
||||||
|
los_clear=los_result.los_clear,
|
||||||
|
geometric_los=los_result.geometric_los,
|
||||||
|
first_fresnel_clearance_pct=los_result.first_fresnel_clearance_pct,
|
||||||
|
fresnel_violations_count=len(los_result.obstructions),
|
||||||
|
geometric_obstructions_count=len(geometric_obstructions),
|
||||||
|
building_obstructions_count=len(building_obstructions),
|
||||||
|
worst_obstruction=_convert_obstruction(los_result.worst_sample),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _convert_obstruction(sample: LosSample | None) -> Obstruction | None:
|
||||||
|
if sample is None:
|
||||||
|
return None
|
||||||
|
return Obstruction(
|
||||||
|
distance_m=sample.distance_m,
|
||||||
|
clearance_m=sample.clearance_m,
|
||||||
|
fresnel_radius_m=sample.fresnel_radius_m,
|
||||||
|
required_clearance_m=sample.required_clearance_m,
|
||||||
|
type=sample.obstruction_type,
|
||||||
|
)
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ def test_link_budget_includes_vegetation_loss(monkeypatch) -> None:
|
|||||||
result = link_service.link_budget(request)
|
result = link_service.link_budget(request)
|
||||||
|
|
||||||
assert result.vegetation_db == 5.0
|
assert result.vegetation_db == 5.0
|
||||||
|
assert result.los_clear is result.fresnel_clear
|
||||||
|
assert result.fresnel_violations_count >= 0
|
||||||
assert isclose(
|
assert isclose(
|
||||||
result.total_loss_db,
|
result.total_loss_db,
|
||||||
result.fspl_db + result.diffraction_db + 5.0,
|
result.fspl_db + result.diffraction_db + 5.0,
|
||||||
@@ -94,3 +96,8 @@ def test_link_budget_includes_diffraction_loss(monkeypatch) -> None:
|
|||||||
assert result.diffraction_db > 0
|
assert result.diffraction_db > 0
|
||||||
assert isclose(result.total_loss_db, result.fspl_db + result.diffraction_db)
|
assert isclose(result.total_loss_db, result.fspl_db + result.diffraction_db)
|
||||||
assert result.fresnel_clear is False
|
assert result.fresnel_clear is False
|
||||||
|
assert result.los_clear is False
|
||||||
|
assert result.geometric_los is False
|
||||||
|
assert result.fresnel_violations_count > 0
|
||||||
|
assert result.geometric_obstructions_count > 0
|
||||||
|
assert result.worst_obstruction is not None
|
||||||
|
|||||||
Reference in New Issue
Block a user