71 lines
2.3 KiB
Python
71 lines
2.3 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
from typing import Any
|
|
|
|
from app.config import settings
|
|
from app.models.schemas import CoverageRequest, GeoAntenna
|
|
from app.services.geodesy import grid_in_circle, line_distance_m
|
|
from app.services.link_calculator import LinkCalculator
|
|
|
|
|
|
class CoverageService:
|
|
def __init__(self, link_calculator: LinkCalculator) -> None:
|
|
self.link_calculator = link_calculator
|
|
|
|
async def calculate_coverage(self, request: CoverageRequest) -> dict[str, Any]:
|
|
grid_points = grid_in_circle(
|
|
request.origin.lat,
|
|
request.origin.lng,
|
|
request.radius_m,
|
|
request.resolution_m,
|
|
)
|
|
|
|
if len(grid_points) > settings.max_grid_points:
|
|
raise ValueError(
|
|
f"Grid has {len(grid_points)} points, exceeds max_grid_points={settings.max_grid_points}"
|
|
)
|
|
|
|
semaphore = asyncio.Semaphore(settings.coverage_concurrency)
|
|
features: list[dict[str, Any]] = []
|
|
|
|
async def evaluate_point(lat: float, lng: float) -> dict[str, Any]:
|
|
async with semaphore:
|
|
to_point = GeoAntenna(lat=lat, lng=lng, antenna_height_m=request.origin.antenna_height_m)
|
|
result = await self.link_calculator.calculate_link(
|
|
from_pt=request.origin,
|
|
to_pt=to_point,
|
|
frequency_mhz=request.frequency_mhz,
|
|
fresnel_clearance=request.fresnel_clearance,
|
|
tx_power_dbm=request.tx_power_dbm,
|
|
rx_sensitivity_dbm=request.rx_sensitivity_dbm,
|
|
sample_step_m=request.sample_step_m,
|
|
)
|
|
|
|
covered = result.los and result.fresnel_clear and result.link_budget.status != "poor"
|
|
distance = line_distance_m(request.origin.lat, request.origin.lng, lat, lng)
|
|
|
|
return {
|
|
"type": "Feature",
|
|
"geometry": {
|
|
"type": "Point",
|
|
"coordinates": [round(lng, 6), round(lat, 6)],
|
|
},
|
|
"properties": {
|
|
"covered": covered,
|
|
"los": result.los,
|
|
"fresnel_clear": result.fresnel_clear,
|
|
"margin_db": result.link_budget.margin_db,
|
|
"status": result.link_budget.status,
|
|
"distance_m": round(distance, 1),
|
|
},
|
|
}
|
|
|
|
tasks = [evaluate_point(lat, lng) for lat, lng in grid_points]
|
|
features = await asyncio.gather(*tasks)
|
|
|
|
return {
|
|
"type": "FeatureCollection",
|
|
"features": features,
|
|
}
|