added obstacles
This commit is contained in:
@@ -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'
|
||||
```
|
||||
Reference in New Issue
Block a user