632 lines
20 KiB
Markdown
632 lines
20 KiB
Markdown
# RF Propagation API
|
||
|
||
Документ описывает текущий HTTP API сервиса. Базовый префикс API: `/api/v1`.
|
||
|
||
Примеры ниже предполагают, что сервис опубликован на `http://localhost:5603`.
|
||
Если используется дефолтный порт compose, замени его на `8000`.
|
||
|
||
## Состояние Реализации
|
||
|
||
- Работает: healthcheck, OpenAPI, DEM elevation/profile, OSM buildings query, landcover/canopy path sampling, terrain LOS/Fresnel/diffraction с учётом DEM и buildings, link budget (`manual`, `itm`, `p452`) с P.676 atmospheric loss и P.833 vegetation, antenna omni/sector/file, async coverage/viewshed jobs через Redis + Celery.
|
||
- Частично работает: coverage raster export (`format=geotiff|png`), canopy bootstrap script (landcover загружается, canopy — вручную).
|
||
- Пока не реализовано: полный P.1812 с ITU digital maps (используется terrain-based ITM + clutter), WhiteboxTools viewshed (используется `gdal_viewshed`).
|
||
|
||
## Общие Правила
|
||
|
||
- Формат запросов и ответов: JSON.
|
||
- Координаты входа/выхода: WGS84, `lat`/`lon` в градусах.
|
||
- GeoJSON coordinates: стандартный порядок `[lon, lat]`.
|
||
- Высота антенн: `height_agl`, метры над землёй.
|
||
- Частота в HTTP API: `frequency_mhz`.
|
||
- Лимит `samples` для profile/LOS/landcover: `2..2048`.
|
||
- DEM берётся из `DEM_PATH`, по умолчанию `/data/dem`.
|
||
- Buildings берутся из PostGIS table `buildings`.
|
||
- Vegetation loss считается по формуле P.833 с коэффициентами из env:
|
||
`P833_GAMMA_DB_PER_M` и `P833_MAX_ATTENUATION_DB`.
|
||
|
||
Ошибки возвращаются в machine-friendly формате:
|
||
|
||
```json
|
||
{
|
||
"detail": {
|
||
"code": "NOT_IMPLEMENTED",
|
||
"detail": "..."
|
||
}
|
||
}
|
||
```
|
||
|
||
## 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)}'
|
||
```
|
||
|
||
## Data Status
|
||
|
||
### `GET /api/v1/status/data`
|
||
|
||
Проверяет, какие источники данных доступны API.
|
||
|
||
```bash
|
||
curl -s http://localhost:5603/api/v1/status/data | jq
|
||
```
|
||
|
||
Пример ответа:
|
||
|
||
```json
|
||
{
|
||
"dem": {"configured": true, "path": "/data/dem", "files_count": 36},
|
||
"landcover": {"configured": true, "path": "/data/landcover", "files_count": 6},
|
||
"canopy": {"configured": false, "path": "/data/canopy", "files_count": 0},
|
||
"postgis": {"configured": true, "buildings_count": 2688698, "error": null}
|
||
}
|
||
```
|
||
|
||
Ключевые вычислительные ответы (`terrain/profile`, `terrain/los`, `landcover/path`, `link/budget`)
|
||
также возвращают поле `data_sources`, чтобы интегратор видел, какие слои реально участвовали
|
||
в расчёте.
|
||
|
||
## 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`: высота кроны из `CANOPY_PATH`, если canopy raster доступен.
|
||
- `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,
|
||
"include_vegetation": true,
|
||
"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,
|
||
"include_vegetation": true,
|
||
"k_factor": 1.333,
|
||
"samples": 128
|
||
}' | jq '{
|
||
los_clear,
|
||
geometric_los,
|
||
fresnel_violations_count,
|
||
geometric_obstructions_count,
|
||
building_obstructions_count,
|
||
worst_obstruction,
|
||
fresnel_profile: .fresnel_profile[0:3],
|
||
diffraction_loss_db,
|
||
vegetation_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_profile`: полный профиль по всем `samples` для построения графика зоны Френеля.
|
||
- `fresnel_violations_count`: количество нарушений зоны Френеля.
|
||
- `geometric_obstructions_count`: количество геометрических пересечений.
|
||
- `building_obstructions_count`: количество геометрических препятствий типа `building`.
|
||
- `worst_obstruction`: худшая точка по запасу относительно требуемого Fresnel clearance.
|
||
- `diffraction_loss_db`: текущая Bullington-style оценка knife-edge loss.
|
||
- `vegetation_loss_db`: P.833 attenuation по `vegetation_depth_m` из WorldCover, если `include_vegetation=true`.
|
||
|
||
Поля obstruction:
|
||
|
||
```json
|
||
{
|
||
"distance_m": 1031.5257643738905,
|
||
"clearance_m": -11.086004102305118,
|
||
"fresnel_radius_m": 15.730079622922505,
|
||
"required_clearance_m": 9.438047773753503,
|
||
"type": "building"
|
||
}
|
||
```
|
||
|
||
Поля `fresnel_profile` для графика:
|
||
|
||
```json
|
||
{
|
||
"i": 12,
|
||
"lat": 59.93623,
|
||
"lon": 30.30746,
|
||
"distance_m": 312.5,
|
||
"surface_m": 18.4,
|
||
"path_height_m": 29.7,
|
||
"obstacle_height_m": 18.42,
|
||
"clearance_m": 11.28,
|
||
"fresnel_radius_m": 11.9,
|
||
"required_clearance_m": 7.14,
|
||
"fresnel_upper_60pct_m": 36.84,
|
||
"fresnel_upper_100pct_m": 41.6,
|
||
"fresnel_lower_60pct_m": 22.56,
|
||
"fresnel_lower_100pct_m": 17.8,
|
||
"type": "terrain"
|
||
}
|
||
```
|
||
|
||
Для отрисовки профиля обычно достаточно:
|
||
|
||
- `distance_m` по X.
|
||
- `surface_m` как земля/DSM.
|
||
- `path_height_m` как прямая TX-RX.
|
||
- `fresnel_lower_60pct_m` и `fresnel_upper_60pct_m` как требуемая свободная зона.
|
||
- `fresnel_lower_100pct_m` и `fresnel_upper_100pct_m` как полная первая зона Френеля.
|
||
- точки, где `clearance_m < required_clearance_m`, подсветить как нарушения.
|
||
|
||
### `POST /api/v1/terrain/fresnel-slice`
|
||
|
||
Возвращает горизонтальную GeoJSON-геометрию зоны Френеля и здания, которые
|
||
пересекают эту область. Endpoint нужен для визуального анализа потенциальных
|
||
отражающих объектов рядом с трассой.
|
||
|
||
```bash
|
||
curl -s -X POST http://localhost:5603/api/v1/terrain/fresnel-slice \
|
||
-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_fraction": 0.6,
|
||
"include_buildings": true,
|
||
"include_canopy": true,
|
||
"samples": 128,
|
||
"building_limit": 5000
|
||
}' | jq '.fresnel_geojson.geometry.type, (.buildings_geojson.features | length)'
|
||
```
|
||
|
||
Ключевые поля:
|
||
|
||
- `path_geojson`: линия TX-RX.
|
||
- `fresnel_geojson`: Polygon/MultiPolygon горизонтального среза зоны Френеля.
|
||
- `buildings_geojson`: здания PostGIS, пересекающие `fresnel_geojson`, с `intersection_area_m2`.
|
||
- `profile`: тот же набор точек Френеля, что удобно использовать для вертикального графика.
|
||
|
||
## 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, terrain/buildings diffraction по DEM/PostGIS profile,
|
||
и vegetation attenuation по WorldCover при `include_vegetation=true`.
|
||
|
||
```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`
|
||
- `fresnel_quality`: `clear`, `partial` или `blocked`
|
||
- `link_viable`
|
||
- `los_clear`
|
||
- `geometric_los`
|
||
- `first_fresnel_clearance_pct`
|
||
- `fresnel_violations_count`
|
||
- `geometric_obstructions_count`
|
||
- `building_obstructions_count`
|
||
- `worst_obstruction`
|
||
|
||
При `include_vegetation=true` API сэмплит WorldCover вдоль трассы и добавляет
|
||
P.833 vegetation attenuation в `vegetation_db`. Если landcover raster отсутствует,
|
||
значение остаётся `0`, чтобы link budget продолжал работать.
|
||
|
||
При `include_buildings=true` API также строит terrain surface profile с DEM и
|
||
OSM buildings, считает Fresnel/LOS и добавляет Bullington-style diffraction loss
|
||
в `diffraction_db`. Поле `fresnel_clear` берётся из этого же анализа.
|
||
|
||
`link_viable` считается по фактическому `fade_margin_db > 0` после применённых
|
||
потерь. Состояние зоны Френеля не выключает линк напрямую, а отдаётся отдельным
|
||
качеством:
|
||
|
||
- `clear`: нет нарушений требуемых 60% первой зоны Френеля.
|
||
- `partial`: геометрическая LOS есть, но 60% зоны Френеля частично перекрыта.
|
||
- `blocked`: геометрическая линия TX-RX пересекает terrain/buildings/canopy.
|
||
|
||
Диагностические поля `geometric_los`, `worst_obstruction` и счётчики obstruction
|
||
помогают понять, почему качество Френеля ухудшилось.
|
||
|
||
Модели `p452` и `itm` используют ITU-R P.452 (pycraf) и Longley-Rice ITM (itmlogic) поверх DEM profile.
|
||
Поле `atmospheric_db` считается по ITU-R P.676 (itur). Флаг `include_canopy` учитывает canopy raster в surface profile.
|
||
|
||
## Antenna
|
||
|
||
### `POST /api/v1/antenna/pattern`
|
||
|
||
Нормализует antenna pattern. Поддержаны `omni`, `sector` и `file` (CSV: `azimuth_deg,elevation_deg,gain_dbi`).
|
||
|
||
```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"` читает CSV с колонками `azimuth_deg,elevation_deg,gain_dbi` (относительный gain).
|
||
|
||
### `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`
|
||
|
||
Сэмплит landcover raster вдоль трассы и сворачивает точки в сегменты.
|
||
Canopy raster используется опционально: если файлов нет, endpoint всё равно
|
||
работает, но `canopy_height_m` будет `null`.
|
||
|
||
Ожидаемые данные:
|
||
|
||
- `LANDCOVER_PATH`: GeoTIFF/COG с кодами ESA WorldCover.
|
||
- `CANOPY_PATH`: GeoTIFF/COG с высотой кроны в метрах.
|
||
|
||
Поддерживаемые классы ESA WorldCover:
|
||
|
||
- `10`: `tree_cover`
|
||
- `20`: `shrubland`
|
||
- `30`: `grassland`
|
||
- `40`: `cropland`
|
||
- `50`: `built_up`
|
||
- `60`: `bare_sparse_vegetation`
|
||
- `70`: `snow_ice`
|
||
- `80`: `water`
|
||
- `90`: `herbaceous_wetland`
|
||
- `95`: `mangroves`
|
||
- `100`: `moss_lichen`
|
||
|
||
```bash
|
||
curl -s -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
|
||
}' | jq
|
||
```
|
||
|
||
Ключевые поля ответа:
|
||
|
||
- `segments`: участки с одинаковым landcover class.
|
||
- `segments[].class`: класс покрова.
|
||
- `segments[].forest_type`: маппится из WorldCover class для P.833 vegetation loss.
|
||
- `segments[].canopy_height_m`: средняя высота кроны на сегменте, если canopy raster доступен.
|
||
- `vegetation_depth_m`: суммарная длина участков `tree_cover`/`mangroves` по трассе.
|
||
|
||
Если landcover raster отсутствует или не покрывает трассу, endpoint возвращает `501 Not Implemented`.
|
||
|
||
## Viewshed
|
||
|
||
### `POST /api/v1/viewshed`
|
||
|
||
Ставит async job в Celery. Viewshed считается через `gdal_viewshed` по DEM,
|
||
результат — GeoJSON или GeoTIFF в `JOBS_OUTPUT_PATH`. `radius_m` остаётся
|
||
основным радиусом запроса, а опциональный `calc_radius_m` позволяет отдельно
|
||
задать радиус DEM-мозаики и параметр `gdal_viewshed -md`; если он не задан,
|
||
используется `radius_m`.
|
||
|
||
```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,
|
||
"calc_radius_m": 20000,
|
||
"target_height_agl": 2,
|
||
"surface": "dsm",
|
||
"k_factor": 1.333,
|
||
"format": "geojson"
|
||
}' | jq
|
||
```
|
||
|
||
Ответ:
|
||
|
||
```json
|
||
{
|
||
"job_id": "..."
|
||
}
|
||
```
|
||
|
||
## Coverage
|
||
|
||
### `POST /api/v1/coverage`
|
||
|
||
Ставит async job в Celery. Radial coverage с моделями `fspl`, `itm`, `p1812` и antenna gain (omni/sector/file). Результат — GeoJSON contour polygons.
|
||
|
||
```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 из Redis. Jobs переживают рестарт API, TTL задаётся `JOB_TTL_SECONDS` (по умолчанию 7 дней).
|
||
|
||
```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;"
|
||
```
|
||
|
||
## Частые Диагностические Команды
|
||
|
||
Полный smoke-check:
|
||
|
||
```bash
|
||
API_BASE=http://localhost:5603 sh scripts/smoke_api.sh
|
||
```
|
||
|
||
Проверить, что 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'
|
||
```
|