Files
RadioPropagationApi/API.md
T
2026-06-23 12:57:42 +03:00

13 KiB
Raw Blame History

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 поднят.

curl -s http://localhost:5603/health | jq

Ответ:

{
  "status": "ok"
}

OpenAPI

GET /openapi.json

Машиночитаемая схема FastAPI.

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-слой ещё не подключён.
curl -s "http://localhost:5603/api/v1/terrain/elevation?lat=59.9386&lon=30.3141&surface=dtm" | jq

Пример ответа:

{
  "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.

Тело запроса:

{
  "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
}

Пример:

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, первую зону Френеля и дифракционные потери по профилю.

Тело запроса:

{
  "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
}

Пример:

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:

{
  "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:

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'

Вариант по линии с буфером:

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.
  • иначе дефолт по типу здания.

POST /api/v1/link/budget

Считает point-to-point budget. Сейчас рабочий режим: model="manual" с FSPL и заданными потерями по умолчанию 0.

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-модель.

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 для визуализации сектора/луча.

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.

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 ещё не подключён.

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

Ответ:

{
  "job_id": "..."
}

Coverage

POST /api/v1/coverage

Создаёт job-stub. Реальный radial coverage расчёт ещё не подключён.

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

Ответ:

{
  "job_id": "..."
}

Jobs

GET /api/v1/jobs/{job_id}

Возвращает статус job, если job был создан текущим процессом API. Сейчас job storage in-memory, поэтому после рестарта API ранее созданные job исчезают.

curl -s http://localhost:5603/api/v1/jobs/<job_id> | jq

Статусы:

  • queued
  • running
  • done
  • error

Данные И Обслуживание

DEM

Загрузка DEM для СПб и Ленобласти:

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:

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

Проверка количества зданий:

docker compose exec -T postgis psql -U radio -d radio \
  -c "SELECT count(*) FROM buildings;"

Частые Диагностические Команды

Проверить, что API видит PostGIS:

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:

docker compose exec api sh -lc 'ls -lh /data/dem | head'

Проверить список путей OpenAPI:

curl -s http://localhost:5603/openapi.json | jq '.paths | keys'