@mytec: iter2.4 ready for testing
This commit is contained in:
@@ -12,6 +12,7 @@ from app.services.coverage_service import (
|
||||
apply_preset,
|
||||
PRESETS,
|
||||
)
|
||||
from app.services.parallel_coverage_service import CancellationToken
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -59,6 +60,7 @@ async def calculate_coverage(request: CoverageRequest) -> CoverageResponse:
|
||||
|
||||
# Time the calculation
|
||||
start_time = time.time()
|
||||
cancel_token = CancellationToken()
|
||||
|
||||
try:
|
||||
# Calculate with 5-minute timeout
|
||||
@@ -66,7 +68,8 @@ async def calculate_coverage(request: CoverageRequest) -> CoverageResponse:
|
||||
points = await asyncio.wait_for(
|
||||
coverage_service.calculate_coverage(
|
||||
request.sites[0],
|
||||
request.settings
|
||||
request.settings,
|
||||
cancel_token,
|
||||
),
|
||||
timeout=300.0
|
||||
)
|
||||
@@ -74,12 +77,17 @@ async def calculate_coverage(request: CoverageRequest) -> CoverageResponse:
|
||||
points = await asyncio.wait_for(
|
||||
coverage_service.calculate_multi_site_coverage(
|
||||
request.sites,
|
||||
request.settings
|
||||
request.settings,
|
||||
cancel_token,
|
||||
),
|
||||
timeout=300.0
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
cancel_token.cancel()
|
||||
raise HTTPException(408, "Calculation timeout (5 min) — try smaller radius or lower resolution")
|
||||
except asyncio.CancelledError:
|
||||
cancel_token.cancel()
|
||||
raise HTTPException(499, "Client disconnected")
|
||||
|
||||
computation_time = time.time() - start_time
|
||||
|
||||
|
||||
@@ -21,25 +21,24 @@ async def get_system_info():
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Check GPU
|
||||
gpu_info = None
|
||||
try:
|
||||
import cupy as cp
|
||||
if cp.cuda.runtime.getDeviceCount() > 0:
|
||||
props = cp.cuda.runtime.getDeviceProperties(0)
|
||||
gpu_info = {
|
||||
"name": props["name"].decode(),
|
||||
"memory_mb": props["totalGlobalMem"] // (1024 * 1024),
|
||||
}
|
||||
except Exception:
|
||||
pass
|
||||
# Check GPU via gpu_service
|
||||
from app.services.gpu_service import gpu_service
|
||||
gpu_info = gpu_service.get_info()
|
||||
|
||||
# Determine parallel backend
|
||||
if ray_available:
|
||||
parallel_backend = "ray"
|
||||
elif cpu_cores > 1:
|
||||
parallel_backend = "process_pool"
|
||||
else:
|
||||
parallel_backend = "sequential"
|
||||
|
||||
return {
|
||||
"cpu_cores": cpu_cores,
|
||||
"parallel_workers": min(cpu_cores, 14),
|
||||
"parallel_backend": "ray" if ray_available else "sequential",
|
||||
"parallel_backend": parallel_backend,
|
||||
"ray_available": ray_available,
|
||||
"ray_initialized": ray_initialized,
|
||||
"gpu": gpu_info,
|
||||
"gpu_enabled": gpu_info is not None,
|
||||
"gpu_available": gpu_info.get("available", False),
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import os
|
||||
import asyncio
|
||||
import math
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
from fastapi.responses import FileResponse
|
||||
@@ -11,6 +13,46 @@ from app.services.los_service import los_service
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
def _build_elevation_grid(min_lat, max_lat, min_lon, max_lon, resolution):
|
||||
"""Build a 2D elevation grid. Runs in thread executor (CPU-bound)."""
|
||||
import numpy as np
|
||||
|
||||
rows = min(resolution, 200)
|
||||
cols = min(resolution, 200)
|
||||
|
||||
lats = np.linspace(max_lat, min_lat, rows) # north to south
|
||||
lons = np.linspace(min_lon, max_lon, cols)
|
||||
|
||||
grid = []
|
||||
min_elev = float('inf')
|
||||
max_elev = float('-inf')
|
||||
|
||||
for lat in lats:
|
||||
row = []
|
||||
for lon in lons:
|
||||
elev = terrain_service.get_elevation_sync(float(lat), float(lon))
|
||||
row.append(elev)
|
||||
if elev < min_elev:
|
||||
min_elev = elev
|
||||
if elev > max_elev:
|
||||
max_elev = elev
|
||||
grid.append(row)
|
||||
|
||||
return {
|
||||
"grid": grid,
|
||||
"rows": rows,
|
||||
"cols": cols,
|
||||
"min_elevation": min_elev if min_elev != float('inf') else 0,
|
||||
"max_elevation": max_elev if max_elev != float('-inf') else 0,
|
||||
"bbox": {
|
||||
"min_lat": min_lat,
|
||||
"max_lat": max_lat,
|
||||
"min_lon": min_lon,
|
||||
"max_lon": max_lon,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@router.get("/elevation")
|
||||
async def get_elevation(
|
||||
lat: float = Query(..., ge=-90, le=90, description="Latitude"),
|
||||
@@ -26,6 +68,42 @@ async def get_elevation(
|
||||
}
|
||||
|
||||
|
||||
@router.get("/elevation-grid")
|
||||
async def get_elevation_grid(
|
||||
min_lat: float = Query(..., ge=-90, le=90, description="South boundary"),
|
||||
max_lat: float = Query(..., ge=-90, le=90, description="North boundary"),
|
||||
min_lon: float = Query(..., ge=-180, le=180, description="West boundary"),
|
||||
max_lon: float = Query(..., ge=-180, le=180, description="East boundary"),
|
||||
resolution: int = Query(100, ge=10, le=200, description="Grid size (rows/cols)"),
|
||||
):
|
||||
"""Get elevation grid for a bounding box. Returns a 2D array for terrain visualization."""
|
||||
if max_lat <= min_lat or max_lon <= min_lon:
|
||||
raise HTTPException(400, "Invalid bbox: max must be greater than min")
|
||||
if (max_lat - min_lat) > 2.0 or (max_lon - min_lon) > 2.0:
|
||||
raise HTTPException(400, "Bbox too large (max 2 degrees per axis)")
|
||||
|
||||
# Ensure terrain tiles are loaded for this area
|
||||
await terrain_service.ensure_tiles_for_bbox(min_lat, min_lon, max_lat, max_lon)
|
||||
|
||||
# Pre-load all tiles that cover the bbox
|
||||
lat_start = int(math.floor(min_lat))
|
||||
lat_end = int(math.floor(max_lat))
|
||||
lon_start = int(math.floor(min_lon))
|
||||
lon_end = int(math.floor(max_lon))
|
||||
for lat_i in range(lat_start, lat_end + 1):
|
||||
for lon_i in range(lon_start, lon_end + 1):
|
||||
tile_name = terrain_service.get_tile_name(lat_i + 0.5, lon_i + 0.5)
|
||||
terrain_service._load_tile(tile_name)
|
||||
|
||||
# Build grid in thread executor (CPU-bound sync calls)
|
||||
loop = asyncio.get_event_loop()
|
||||
result = await loop.run_in_executor(
|
||||
None, _build_elevation_grid,
|
||||
min_lat, max_lat, min_lon, max_lon, resolution,
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
@router.get("/profile")
|
||||
async def get_elevation_profile(
|
||||
lat1: float = Query(..., description="Start latitude"),
|
||||
@@ -87,9 +165,9 @@ async def check_fresnel_clearance(
|
||||
@router.get("/tiles")
|
||||
async def list_cached_tiles():
|
||||
"""List cached SRTM tiles"""
|
||||
tiles = list(terrain_service.cache_dir.glob("*.hgt"))
|
||||
tiles = list(terrain_service.terrain_path.glob("*.hgt"))
|
||||
return {
|
||||
"cache_dir": str(terrain_service.cache_dir),
|
||||
"cache_dir": str(terrain_service.terrain_path),
|
||||
"tiles": [t.stem for t in tiles],
|
||||
"count": len(tiles)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user