added conopy

This commit is contained in:
2026-06-24 09:01:11 +03:00
parent d9c262cee2
commit 7ec7a51648
18 changed files with 652 additions and 34 deletions
+23
View File
@@ -17,6 +17,7 @@ def test_openapi_contains_expected_paths() -> None:
paths = response.json()["paths"]
assert "/api/v1/terrain/profile" in paths
assert "/api/v1/terrain/los" in paths
assert "/api/v1/terrain/fresnel-slice" in paths
assert "/api/v1/link/budget" in paths
assert "/api/v1/coverage" in paths
assert "/api/v1/status/data" in paths
@@ -100,6 +101,28 @@ def test_los_endpoint_returns_obstruction_summary_fields() -> None:
)
def test_fresnel_slice_endpoint_returns_geojson() -> None:
response = client.post(
"/api/v1/terrain/fresnel-slice",
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_fraction": 0.6,
"include_buildings": False,
"include_canopy": False,
"samples": 16,
},
)
assert response.status_code == 200
data = response.json()
assert data["path_geojson"]["type"] == "LineString"
assert data["fresnel_geojson"]["type"] == "Feature"
assert data["fresnel_geojson"]["geometry"]["type"] in {"Polygon", "MultiPolygon"}
assert data["buildings_geojson"]["type"] == "FeatureCollection"
assert len(data["profile"]) == 16
def test_manual_link_budget_endpoint() -> None:
response = client.post(
"/api/v1/link/budget",
+81
View File
@@ -0,0 +1,81 @@
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
import rasterio
from rasterio.transform import from_origin
from app.core.geo import GeoPoint
from app.core.viewshed import _find_dem_tiles, _radius_bbox
sys.path.append(str(Path(__file__).resolve().parents[2] / "scripts"))
import bootstrap_canopy # noqa: E402
def _write_dem(path: Path, west: float) -> None:
data = np.ones((10, 10), dtype="float32")
transform = from_origin(west, 60.0, 0.01, 0.01)
with rasterio.open(
path,
"w",
driver="GTiff",
height=data.shape[0],
width=data.shape[1],
count=1,
dtype=data.dtype,
crs="EPSG:4326",
transform=transform,
nodata=-9999.0,
) as dataset:
dataset.write(data, 1)
def test_viewshed_dem_search_uses_radius_bbox(tmp_path: Path) -> None:
_write_dem(tmp_path / "west.tif", 30.0)
_write_dem(tmp_path / "east.tif", 30.1)
bbox = _radius_bbox(GeoPoint(lat=59.95, lon=30.095), 2_000)
tiles = _find_dem_tiles(tmp_path, bbox)
assert {tile.name for tile in tiles} == {"west.tif", "east.tif"}
def test_canopy_selects_intersecting_tiles_from_geojson_index() -> None:
index = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"bbox": [30.0, 59.0, 31.0, 60.0],
"properties": {"filename": "tile_a.tif"},
},
{
"type": "Feature",
"bbox": [40.0, 59.0, 41.0, 60.0],
"properties": {"filename": "tile_b.tif"},
},
],
}
urls = bootstrap_canopy.select_tile_urls(index, (30.2, 59.2, 30.4, 59.4))
assert len(urls) == 1
assert urls[0].endswith("/tile_a.tif")
def test_canopy_urls_file_ignores_comments(tmp_path: Path) -> None:
urls_file = tmp_path / "urls.txt"
urls_file.write_text(
"# comment\n"
"https://example.com/a.tif\n"
"\n"
"https://example.com/b.tif\n",
encoding="utf-8",
)
assert bootstrap_canopy.urls_from_file(urls_file) == [
"https://example.com/a.tif",
"https://example.com/b.tif",
]