added obstacles

This commit is contained in:
2026-06-23 12:57:42 +03:00
parent 6be9810404
commit b92bb1ceba
2 changed files with 456 additions and 0 deletions
+455
View File
@@ -0,0 +1,455 @@
# RF Propagation API
Документ описывает текущий HTTP API сервиса. Базовый префикс API: `/api/v1`.
Примеры ниже предполагают, что сервис опубликован на `http://localhost:5603`.
Если используется дефолтный порт compose, замени его на `8000`.
## Состояние Реализации
- Работает: healthcheck, OpenAPI, DEM elevation/profile, OSM buildings query, terrain LOS/Fresnel/diffraction с учётом DEM и buildings, manual link budget, antenna sector/beam helpers.
- Частично работает: coverage/viewshed создают job-stub, но тяжёлый расчёт ещё не реализован.
- Пока не реализовано: landcover/canopy sampling, vegetation attenuation по реальным данным, ITM/P.1812/P.452/P.676, реальные async results для coverage/viewshed.
## Общие Правила
- Формат запросов и ответов: JSON.
- Координаты входа/выхода: WGS84, `lat`/`lon` в градусах.
- GeoJSON coordinates: стандартный порядок `[lon, lat]`.
- Высота антенн: `height_agl`, метры над землёй.
- Частота в HTTP API: `frequency_mhz`.
- DEM берётся из `DEM_PATH`, по умолчанию `/data/dem`.
- Buildings берутся из PostGIS table `buildings`.
## Health
### `GET /health`
Проверка, что API поднят.
```bash
curl -s http://localhost:5603/health | jq
```
Ответ:
```json
{
"status": "ok"
}
```
## OpenAPI
### `GET /openapi.json`
Машиночитаемая схема FastAPI.
```bash
curl -s http://localhost:5603/openapi.json | jq '{title: .info.title, paths: (.paths | keys)}'
```
## Terrain
### `GET /api/v1/terrain/elevation`
Возвращает высоту DEM в точке.
Query parameters:
- `lat`: широта.
- `lon`: долгота.
- `surface`: `dtm` или `dsm`. Сейчас оба читаются из DEM-растра; отдельный DSM-слой ещё не подключён.
```bash
curl -s "http://localhost:5603/api/v1/terrain/elevation?lat=59.9386&lon=30.3141&surface=dtm" | jq
```
Пример ответа:
```json
{
"lat": 59.9386,
"lon": 30.3141,
"elevation_m": 3.099534749984741,
"surface": "dtm"
}
```
### `POST /api/v1/terrain/profile`
Строит геодезический профиль между двумя точками. Если `include_buildings=true`, точки профиля, попавшие внутрь OSM building polygon, получают `building_m`.
Тело запроса:
```json
{
"start": {"lat": 59.935, "lon": 30.305},
"end": {"lat": 59.945, "lon": 30.325},
"samples": 64,
"include_buildings": true,
"include_canopy": false,
"k_factor": 1.333
}
```
Пример:
```bash
curl -s -X POST http://localhost:5603/api/v1/terrain/profile \
-H 'Content-Type: application/json' \
-d '{
"start": {"lat": 59.935, "lon": 30.305},
"end": {"lat": 59.945, "lon": 30.325},
"samples": 64,
"include_buildings": true,
"include_canopy": false,
"k_factor": 1.333
}' | jq '.distance_m, [.samples[] | select(.building_m > 0)] | length'
```
Ключевые поля ответа:
- `distance_m`: длина трассы.
- `samples`: массив точек профиля.
- `ground_m`: высота DEM.
- `building_m`: высота здания над землёй.
- `canopy_m`: высота кроны, пока всегда `0`.
- `surface_m`: `ground_m + max(building_m, canopy_m)`.
- `path_geojson`: GeoJSON LineString.
### `POST /api/v1/terrain/los`
Проверяет LOS, первую зону Френеля и дифракционные потери по профилю.
Тело запроса:
```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_clearance": 0.6,
"include_buildings": true,
"include_canopy": false,
"k_factor": 1.333,
"samples": 128
}
```
Пример:
```bash
curl -s -X POST http://localhost:5603/api/v1/terrain/los \
-H 'Content-Type: application/json' \
-d '{
"tx": {"lat": 59.935, "lon": 30.305, "height_agl": 30},
"rx": {"lat": 59.945, "lon": 30.325, "height_agl": 2},
"frequency_mhz": 433,
"fresnel_clearance": 0.6,
"include_buildings": true,
"include_canopy": false,
"k_factor": 1.333,
"samples": 128
}' | jq '{
los_clear,
geometric_los,
fresnel_violations_count,
geometric_obstructions_count,
building_obstructions_count,
worst_obstruction,
diffraction_loss_db
}'
```
Ключевые поля ответа:
- `los_clear`: `true`, если нет нарушений требуемого Fresnel clearance.
- `geometric_los`: `true`, если геометрическая линия TX-RX не пересекает поверхность.
- `first_fresnel_clearance_pct`: минимальный процент доступного просвета относительно требуемого `fresnel_clearance * F1`.
- `obstructions`: все точки, где `clearance_m < required_clearance_m`.
- `geometric_obstructions`: только точки, где `clearance_m < 0`.
- `fresnel_violations_count`: количество нарушений зоны Френеля.
- `geometric_obstructions_count`: количество геометрических пересечений.
- `building_obstructions_count`: количество геометрических препятствий типа `building`.
- `worst_obstruction`: худшая точка по запасу относительно требуемого Fresnel clearance.
- `diffraction_loss_db`: текущая Bullington-style оценка knife-edge loss.
Поля obstruction:
```json
{
"distance_m": 1031.5257643738905,
"clearance_m": -11.086004102305118,
"fresnel_radius_m": 15.730079622922505,
"required_clearance_m": 9.438047773753503,
"type": "building"
}
```
## Buildings
### `POST /api/v1/buildings/query`
Возвращает OSM buildings из PostGIS как GeoJSON FeatureCollection.
Вариант по bbox:
```bash
curl -s -X POST http://localhost:5603/api/v1/buildings/query \
-H 'Content-Type: application/json' \
-d '{"bbox":[30.30,59.93,30.33,59.95]}' | jq '.features | length'
```
Вариант по линии с буфером:
```bash
curl -s -X POST http://localhost:5603/api/v1/buildings/query \
-H 'Content-Type: application/json' \
-d '{
"path": {
"type": "LineString",
"coordinates": [[30.305, 59.935], [30.325, 59.945]]
},
"buffer_m": 50
}' | jq '.features | length'
```
Feature properties:
- `height_m`: высота здания в метрах.
- `levels`: этажность, если известна.
- `building_type`: значение OSM tag `building`.
- `source`: `measured`, если был tag `height`; иначе `estimated`.
Высота считается при импорте:
- `height`, если есть.
- иначе `building:levels * 3.0`.
- иначе дефолт по типу здания.
## Link Budget
### `POST /api/v1/link/budget`
Считает point-to-point budget. Сейчас рабочий режим: `model="manual"` с FSPL и заданными потерями по умолчанию `0`.
```bash
curl -s -X POST http://localhost:5603/api/v1/link/budget \
-H 'Content-Type: application/json' \
-d '{
"tx": {"lat": 60.17, "lon": 24.94, "height_agl": 30, "power_dbm": 37, "gain_dbi": 8},
"rx": {"lat": 60.25, "lon": 25.10, "height_agl": 2, "gain_dbi": 2, "sensitivity_dbm": -110},
"frequency_mhz": 433,
"model": "manual",
"include_buildings": true,
"include_vegetation": true,
"k_factor": 1.333
}' | jq
```
Ключевые поля ответа:
- `distance_km`
- `fspl_db`
- `diffraction_db`
- `vegetation_db`
- `atmospheric_db`
- `total_loss_db`
- `rx_power_dbm`
- `fade_margin_db`
- `fresnel_clear`
- `link_viable`
Модели `p452` и `itm` пока возвращают `501 Not Implemented`.
## Antenna
### `POST /api/v1/antenna/pattern`
Нормализует antenna pattern. Сейчас поддержаны `omni` и простая `sector`-модель.
```bash
curl -s -X POST http://localhost:5603/api/v1/antenna/pattern \
-H 'Content-Type: application/json' \
-d '{
"pattern": "sector",
"azimuth_deg": 90,
"tilt_deg": 0,
"gain_dbi": 8,
"beamwidth_h": 65,
"beamwidth_v": 15
}' | jq '.samples[:5]'
```
`pattern="file"` пока не реализован.
### `POST /api/v1/antenna/beam`
Возвращает GeoJSON polygon для визуализации сектора/луча.
```bash
curl -s -X POST http://localhost:5603/api/v1/antenna/beam \
-H 'Content-Type: application/json' \
-d '{
"origin": {"lat": 59.935, "lon": 30.305},
"radius_m": 5000,
"antenna": {
"pattern": "sector",
"azimuth_deg": 90,
"tilt_deg": 0,
"gain_dbi": 8,
"beamwidth_h": 65,
"beamwidth_v": 15
}
}' | jq '.geojson.geometry.type'
```
## Landcover
### `POST /api/v1/landcover/path`
Контракт есть, но raster sampling landcover/canopy пока не реализован. Сейчас endpoint возвращает `501 Not Implemented`.
```bash
curl -i -X POST http://localhost:5603/api/v1/landcover/path \
-H 'Content-Type: application/json' \
-d '{
"start": {"lat": 59.935, "lon": 30.305},
"end": {"lat": 59.945, "lon": 30.325},
"samples": 256
}'
```
## Viewshed
### `POST /api/v1/viewshed`
Создаёт job-stub. Реальный WhiteboxTools/GDAL viewshed ещё не подключён.
```bash
curl -s -X POST http://localhost:5603/api/v1/viewshed \
-H 'Content-Type: application/json' \
-d '{
"observer": {"lat": 59.935, "lon": 30.305, "height_agl": 30},
"radius_m": 15000,
"target_height_agl": 2,
"surface": "dsm",
"k_factor": 1.333,
"format": "geojson"
}' | jq
```
Ответ:
```json
{
"job_id": "..."
}
```
## Coverage
### `POST /api/v1/coverage`
Создаёт job-stub. Реальный radial coverage расчёт ещё не подключён.
```bash
curl -s -X POST http://localhost:5603/api/v1/coverage \
-H 'Content-Type: application/json' \
-d '{
"tx": {"lat": 59.935, "lon": 30.305, "height_agl": 30, "power_dbm": 40, "frequency_mhz": 433},
"antenna": {"pattern": "omni", "azimuth_deg": 0, "tilt_deg": 0, "gain_dbi": 8},
"rx": {"height_agl": 2, "sensitivity_dbm": -110, "gain_dbi": 2},
"model": "fspl",
"environment": "urban",
"radius_m": 20000,
"azimuth_step_deg": 1,
"range_step_m": 30,
"include_buildings": true,
"include_vegetation": false,
"levels_dbm": [-90, -100, -110],
"format": "geojson"
}' | jq
```
Ответ:
```json
{
"job_id": "..."
}
```
## Jobs
### `GET /api/v1/jobs/{job_id}`
Возвращает статус job, если job был создан текущим процессом API. Сейчас job storage in-memory, поэтому после рестарта API ранее созданные job исчезают.
```bash
curl -s http://localhost:5603/api/v1/jobs/<job_id> | jq
```
Статусы:
- `queued`
- `running`
- `done`
- `error`
## Данные И Обслуживание
### DEM
Загрузка DEM для СПб и Ленобласти:
```bash
python scripts/bootstrap_dem.py --bbox 27.3,58.4,35.8,61.4 --output-dir data/dem
docker compose restart api worker
```
### Buildings
Загрузка OSM PBF:
```bash
mkdir -p data/osm
wget -O data/osm/northwestern-fed-district-latest.osm.pbf \
https://download.geofabrik.de/russia/northwestern-fed-district-latest.osm.pbf
sh scripts/load_buildings.sh data/osm/northwestern-fed-district-latest.osm.pbf
docker compose restart api worker
```
Проверка количества зданий:
```bash
docker compose exec -T postgis psql -U radio -d radio \
-c "SELECT count(*) FROM buildings;"
```
## Частые Диагностические Команды
Проверить, что API видит PostGIS:
```bash
docker compose exec api python -c "
from sqlalchemy import create_engine, text
from app.config import get_settings
e = create_engine(get_settings().database_url)
with e.connect() as c:
print(c.execute(text('SELECT count(*) FROM buildings')).scalar())
"
```
Проверить, что DEM смонтирован в API container:
```bash
docker compose exec api sh -lc 'ls -lh /data/dem | head'
```
Проверить список путей OpenAPI:
```bash
curl -s http://localhost:5603/openapi.json | jq '.paths | keys'
```