Complete regional raster exploration
This commit is contained in:
@@ -18,7 +18,7 @@ from app.core.errors import AppError
|
||||
from app.db.session import get_db
|
||||
from app.main import app
|
||||
from app.models import Area, Dataset, DatasetVersion, Job, Project
|
||||
from app.schemas.dhmv import DhmvAcquireRequest, TerrainSelectionRequest
|
||||
from app.schemas.dhmv import DhmvAcquireRequest, TerrainPartitionSelectionRequest, TerrainSelectionRequest
|
||||
from app.services.dhmv_acquisition_service import DhmvAcquisitionService
|
||||
from app.services.terrain_analysis_service import TerrainAnalysisService
|
||||
|
||||
@@ -39,6 +39,9 @@ class FakeQuery:
|
||||
def first(self):
|
||||
return self.result
|
||||
|
||||
def all(self):
|
||||
return self.result if isinstance(self.result, list) else []
|
||||
|
||||
|
||||
class FakeSession:
|
||||
def __init__(self, rows=None, query_result=None):
|
||||
@@ -115,6 +118,23 @@ def elevation_tiff(*, left: float, top: float, width: int, height: int, resoluti
|
||||
return memory.read()
|
||||
|
||||
|
||||
def constant_elevation_tiff(*, left: float, top: float, value: float) -> bytes:
|
||||
values = np.full((20, 20), value, dtype="float32")
|
||||
with MemoryFile() as memory:
|
||||
with memory.open(
|
||||
driver="GTiff",
|
||||
width=20,
|
||||
height=20,
|
||||
count=1,
|
||||
dtype="float32",
|
||||
crs="EPSG:31370",
|
||||
transform=from_origin(left, top, 5.0, 5.0),
|
||||
nodata=-9999.0,
|
||||
) as output:
|
||||
output.write(values, 1)
|
||||
return memory.read()
|
||||
|
||||
|
||||
def edge_elevation_tiff(*, left: float, top: float, x_resolution: float, y_resolution: float = 5.0) -> bytes:
|
||||
rows, columns = np.indices((20, 20))
|
||||
values = (20.0 + columns * 0.5 + rows).astype("float32")
|
||||
@@ -359,6 +379,61 @@ def test_terrain_analysis_returns_governed_elevation_relief_and_slope(tmp_path)
|
||||
assert "Waterdiepte" in result["limitation_message"]
|
||||
|
||||
|
||||
def test_partitioned_terrain_analysis_is_exact_across_municipality_boundaries(tmp_path) -> None:
|
||||
project_id = uuid4()
|
||||
transformer = Transformer.from_crs("EPSG:31370", "EPSG:4326", always_xy=True)
|
||||
min_x, min_y = transformer.transform(200_000, 210_000)
|
||||
middle_x, _ = transformer.transform(200_100, 210_000)
|
||||
max_x, max_y = transformer.transform(200_200, 210_100)
|
||||
paths = [tmp_path / "left-terrain.tif", tmp_path / "right-terrain.tif"]
|
||||
paths[0].write_bytes(constant_elevation_tiff(left=200_000, top=210_100, value=10.0))
|
||||
paths[1].write_bytes(constant_elevation_tiff(left=200_100, top=210_100, value=20.0))
|
||||
datasets = [
|
||||
Dataset(
|
||||
id=uuid4(),
|
||||
project_id=project_id,
|
||||
area_id=uuid4(),
|
||||
name=path.name,
|
||||
dataset_type="raster",
|
||||
source="official WCS",
|
||||
source_name="digitaal_vlaanderen_dhmv",
|
||||
source_metadata={
|
||||
"product_key": "dtm_1m",
|
||||
"surface_model": "terrain",
|
||||
"bbox_epsg4326": [left, min_y, right, max_y],
|
||||
},
|
||||
status="ready",
|
||||
storage_path=str(path),
|
||||
)
|
||||
for path, left, right in (
|
||||
(paths[0], min_x, middle_x),
|
||||
(paths[1], middle_x, max_x),
|
||||
)
|
||||
]
|
||||
db = FakeSession(query_result=datasets)
|
||||
payload = TerrainPartitionSelectionRequest(
|
||||
bbox={"min_x": min_x, "min_y": min_y, "max_x": max_x, "max_y": max_y, "crs": "EPSG:4326"},
|
||||
product_key="dtm_1m",
|
||||
)
|
||||
|
||||
result = TerrainAnalysisService.analyze_partitions(
|
||||
db,
|
||||
project_id,
|
||||
payload,
|
||||
settings=Settings(_env_file=None),
|
||||
)
|
||||
metrics = {item["metric_key"]: item["metric_value"] for item in result["summary"]["metrics"]}
|
||||
|
||||
assert result["partition_count"] == 2
|
||||
assert set(result["dataset_ids"]) == {str(dataset.id) for dataset in datasets}
|
||||
assert result["sample_count"] >= 790
|
||||
assert metrics["terrain_elevation_mean_m"] == pytest.approx(15.0, abs=0.1)
|
||||
assert metrics["terrain_elevation_min_m"] == 10.0
|
||||
assert metrics["terrain_elevation_max_m"] == 20.0
|
||||
assert metrics["terrain_elevation_p90_m"] == 20.0
|
||||
assert "2 persistente gemeentelijke rasterpartities" in result["limitation_message"]
|
||||
|
||||
|
||||
def test_terrain_analysis_rejects_non_dhmv_raster(tmp_path) -> None:
|
||||
project_id = uuid4()
|
||||
dataset_id = uuid4()
|
||||
@@ -425,6 +500,16 @@ def test_dhmv_endpoints_use_canonical_envelopes(monkeypatch) -> None:
|
||||
"unsupported_metrics": ["water_depth_m", "water_volume_m3"],
|
||||
},
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
TerrainAnalysisService,
|
||||
"analyze_partitions",
|
||||
lambda *_args, **_kwargs: {
|
||||
"dataset_id": str(output_dataset_id),
|
||||
"dataset_ids": [str(output_dataset_id)],
|
||||
"partition_count": 1,
|
||||
"sample_count": 100,
|
||||
},
|
||||
)
|
||||
app.dependency_overrides[get_db] = lambda: db
|
||||
try:
|
||||
products = TestClient(app).get(f"/api/v1/projects/{project_id}/datasets/dhmv/products")
|
||||
@@ -436,6 +521,10 @@ def test_dhmv_endpoints_use_canonical_envelopes(monkeypatch) -> None:
|
||||
f"/api/v1/projects/{project_id}/datasets/{output_dataset_id}/raster/terrain/select",
|
||||
json={"bbox": lambert_bbox_payload().bbox.model_dump()},
|
||||
)
|
||||
regional_terrain = TestClient(app).post(
|
||||
f"/api/v1/projects/{project_id}/datasets/raster/terrain/select",
|
||||
json={"bbox": lambert_bbox_payload().bbox.model_dump(), "product_key": "dtm_1m"},
|
||||
)
|
||||
finally:
|
||||
app.dependency_overrides.clear()
|
||||
|
||||
@@ -450,6 +539,9 @@ def test_dhmv_endpoints_use_canonical_envelopes(monkeypatch) -> None:
|
||||
assert set(terrain.json()) == {"data"}
|
||||
assert terrain.json()["data"]["sample_count"] == 100
|
||||
assert terrain.json()["data"]["unsupported_metrics"] == ["water_depth_m", "water_volume_m3"]
|
||||
assert regional_terrain.status_code == 200
|
||||
assert set(regional_terrain.json()) == {"data"}
|
||||
assert regional_terrain.json()["data"]["partition_count"] == 1
|
||||
assert any(isinstance(item, Job) for item in db.added)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user