Перейти к содержанию

Работа с файлами

Все файлы, которые Аэрозвено принимает и отдаёт. Примеры ниже синтетические: они построены на демо-сценариях продукта и не содержат данных реальных районов работ.

Сводка форматов

Направление Формат Где
Вход JSON-сценарий (собственный формат) «Импорт проекта»; POST /api/scenarios; POST /api/plans
Вход GeoJSON — участки, зоны, граница пространства, площадки «Импорт проекта»
Вход KML / KMZ, в том числе файлы организаторов: участки, зоны ограничений, высотные препятствия «Импорт проекта»
Вход Собственная выгрузка KML / GeoJSON (обратное чтение) «Импорт проекта»
Выход KML плана со слоями сценария экран «Экспорт»; GET /api/plans/{id}/export/kml
Выход GeoJSON плана со слоями сценария экран «Экспорт»; GET /api/plans/{id}/export/geojson
Выход QGC .plan по борту экран «Экспорт»; GET /api/plans/{id}/export/plan?uav=<id>
Выход Путевые точки (текст) по борту экран «Экспорт» (кнопка CSV); GET /api/plans/{id}/export/waypoints?uav=<id>
Выход JSON-план GET /api/plans/{id}
Выход Отчёт валидатора JSON экран «Отчёт валидатора» (кнопка JSON); POST /api/validate
Выход Отчёт PDF по плану или по борту экран «Экспорт» → «Отчёт PDF», печать браузера

Импорт файлов выполняется в браузере: интерфейс разбирает KML, KMZ и GeoJSON, собирает из них сценарий и сохраняет его на сервере через POST /api/scenarios. Отдельного серверного эндпоинта импорта нет.

JSON-сценарий

Сценарий — один JSON-документ со всеми входными данными. Схема строгая: лишнее поле — ошибка (422 SCHEMA_VALIDATION_FAILED с путём поля). Координаты — [долгота, широта] в WGS 84 (crs всегда EPSG:4326). Единицы зашиты в суффикс имени поля: _m — метры, _s — секунды, _ms — м/с, _deg — градусы, _cm — сантиметры, _frac — доля от 0 до 1, _c — °C, _min — минуты.

Пример: s01 — один борт, участок 1×1 км

{
  "schema_version": 1,
  "scenario_id": "s01-simple",
  "name": "Один борт, прямоугольник 1×1 км",
  "crs": "EPSG:4326",
  "start_time": "2026-09-20T06:00:00Z",
  "launch_sites": [{
    "id": "vpp-1", "name": "ВПП-1 (угол полигона)", "position": [37.62, 55.76],
    "elevation_m": 145, "suitable_for": ["multirotor"],
    "turnaround_time_min": 10, "spare_batteries": { "geoscan_gemini": 2 }
  }],
  "landing_sites": [{
    "id": "lzp-reserve", "position": [37.629, 55.769], "elevation_m": 144,
    "reserve": true, "suitable_for": ["multirotor"]
  }],
  "fleet": [{
    "id": "gemini-01", "model": "geoscan_gemini", "payload": "geoscan_pf1b",
    "home": "vpp-1", "available_from": "2026-09-20T06:00:00Z"
  }],
  "tasks": [{
    "id": "t1", "name": "Прямоугольник 1×1 км", "survey_type": "rgb",
    "quality": { "gsd_cm": 3.0 },
    "area": { "type": "Polygon", "coordinates": [[
      [37.62, 55.76], [37.636, 55.76], [37.636, 55.769], [37.62, 55.769], [37.62, 55.76]
    ]]},
    "time_window": null
  }],
  "airspace": {
    "allowed": { "type": "Polygon", "coordinates": [[
      [37.61, 55.755], [37.646, 55.755], [37.646, 55.774], [37.61, 55.774], [37.61, 55.755]
    ]]},
    "no_fly": []
  },
  "weather": { "wind_speed_ms": 0.0, "wind_direction_deg": 270, "temperature_c": 12 },
  "objective": { "criterion": "makespan", "energy_reserve": 0.2, "turn_mode": "lzp", "safety_buffer_m": 25 },
  "solver": { "time_limit_s": 300, "seed": 42 }
}

Поля сценария

Корень

Поле Обязательно Смысл
schema_version да версия формата, сейчас 1
scenario_id, name да идентификатор и название
crs да всегда EPSG:4326
start_time нет начало работ (ISO 8601); нужно для временных окон участков и прогноза ветра
launch_sites[] да, ≥ 1 площадки взлёта (ВПП)
landing_sites[] да, может быть пустым посадочные и резервные площадки
fleet[] да, ≥ 1 парк бортов
tasks[] да, ≥ 1 участки съёмки
airspace да разрешённое пространство и зоны
weather да ветер и температура
objective да критерий оптимизации и запасы безопасности
solver да лимит времени расчёта и зерно
expected нет свободные заметки об ожидаемом результате; расчёт их не читает

Площадка взлёта launch_sites[]

Поле Обязательно Смысл По умолчанию
id да на него ссылается fleet[].home —
position да [lon, lat] —
elevation_m да высота над уровнем моря, м —
suitable_for нет какие типы бортов принимает: multirotor, fixed_wing оба
takeoff_heading_deg нет курс взлёта с катапульты (самолётные борта) —
turnaround_time_min да время оборота между вылетами, минуты 10
spare_batteries нет запасные заряженные АКБ по моделям: {"geoscan_gemini": 2} нет
service_slots нет сколько бортов обслуживается одновременно; null — без ограничения без ограничения
runway нет взлётно-посадочная полоса: threshold_a, threshold_b ([lon, lat]), width_m, surface (asphalt, concrete, grass, gravel, dirt) —

Посадочная площадка landing_sites[]: id, position, elevation_m, reserve (резервная — только для последней посадки), необязательно suitable_for и runway.

Борт fleet[]

Поле Обязательно Смысл
id да идентификатор борта
model да модель из справочника: geoscan_201, geoscan_801, geoscan_gemini, penguin_b и др. (GET /api/fleet)
payload да нагрузка из справочника (GET /api/payloads), должна подходить модели
home да площадка базирования (launch_sites[].id)
available_from нет с какого момента борт доступен
enabled нет участвует ли в расчёте (по умолчанию да)
battery_left_frac нет заряд АКБ перед первым вылетом, 0…1
focal_length_mm нет фокусное расстояние, если у камеры сменный объектив

Участок съёмки tasks[]

Поле Обязательно Смысл
id, name id — да идентификатор и название
survey_type да rgb, multispectral, ir, lidar, geophysical
quality да ровно одно из: gsd_cm (см/пиксель), point_density_per_m2 (лидар), line_spacing_m (геофизика)
area да GeoJSON Polygon или MultiPolygon, с вырезами
overlap_forward_frac, overlap_side_frac нет перекрытия 0,5…1; не заданы — типовые для типа съёмки из справочника нагрузок
angle_deg нет угол галсов; не задан — подбирает планировщик
time_window нет { "from": …, "to": … } — окно, в которое должны уложиться вылеты

Воздушное пространство airspace

Поле Смысл
allowed граница разрешённого пространства (GeoJSON-полигон)
no_fly[] зоны: geometry, altitude_min_m, altitude_max_m (над землёй), zone_kind (no_fly, restricted, temporary, allowed_airspace), необязательно id, name, valid_from, valid_to, source
floor_m, ceiling_m общий пол и потолок, необязательно

Зона «без ограничения сверху» записывается как altitude_max_m: 100000.

Ветер weather: wind_speed_ms, wind_direction_deg (откуда дует: 270 — западный), необязательно temperature_c и gust_ms (порывы сохраняются, но расчёт их не учитывает).

Критерий objective

Поле Смысл По умолчанию
criterion makespan — минимум времени работ; total_flight_time — минимум суммарного налёта; pareto — компромисс makespan
lambda_frac вес времени работ для pareto, 0…1 —
energy_reserve резерв энергии, доля 0,20
turn_mode разворот lzp (с выходом на ЛЗП) или flyby (пролётом) lzp
safety_buffer_m буфер от зон, м 25
zone_vertical_margin_m запас по высоте над и под слоем зоны, м 50
avoid_zones_laterally обходить зоны стороной на любой высоте false

Расчёт solver: time_limit_s (лимит времени, с) и seed (зерно генератора; одинаковое зерно даёт одинаковый план).

Готовые сценарии для образца — 13 демо-сценариев (см. Сценарии использования) и пример 04-scenario-mixed.json в «Импорте проекта».

Файлы организаторов (KML)

Аэрозвено читает три вида KML в формате, который предоставили организаторы хакатона. Роль файла определяется автоматически и её можно поменять в диалоге импорта: если у большинства объектов есть поля Type и Altitudes — это зоны; если у большинства extrude и высота — препятствия; иначе — участки съёмки. Можно выбрать несколько файлов сразу.

Участок съёмки

<Placemark><Polygon><outerBoundaryIs><LinearRing>
  <altitudeMode>clampToGround</altitudeMode>
  <coordinates>37.604,55.767 37.614,55.767 37.614,55.771 37.604,55.771 37.604,55.767</coordinates>
</LinearRing></outerBoundaryIs></Polygon></Placemark>

Каждый полигон (с вырезами innerBoundaryIs) становится участком t1, t2, … с типом rgb и GSD 5 см — их можно поменять после импорта. Вырожденные полигоны пропускаются; узкие (уже 20 м) принимаются и снимаются одним галсом.

Зона ограничений

<Placemark>
  <name>ZONE-001</name>
  <ExtendedData>
    <Data name="Name"><value>ZONE-001</value></Data>
    <Data name="Type"><value>пост_ограничение</value></Data>
    <Data name="Altitudes"><value>От земли до 300 м (1000 фут) AMSL
Кроме санитарной авиации</value></Data>
  </ExtendedData>
  <MultiGeometry><Polygon><outerBoundaryIs><LinearRing>
    <coordinates>37.60,55.75 37.62,55.75 37.62,55.76 37.60,55.76 37.60,55.75</coordinates>
  </LinearRing></outerBoundaryIs></Polygon></MultiGeometry>
</Placemark>
Поле файла Что получается в сценарии
Type вид зоны: запретная_зона → no_fly, пост_ограничение → restricted, врем_ограничение → temporary, прочее → restricted
Altitudes высотный слой: текст вида «От земли до N м … AMSL», «От FLxxx до FLyyy», «На всех высотах» разбирается и пересчитывается в высоту над землёй с запасом в безопасную сторону; эшелон FL = N × 100 × 0,3048 м
Неразобранный текст высот зона на всех высотах; число таких зон показывается в отчёте импорта
Исключения (вторая строка) сохраняются в поле source зоны

Зоны дальше 1,5 км от охвата участков и площадок отбрасываются.

Высотное препятствие

<Placemark>
  <name>OBST-0001 OTHER:COMMUNICATION_TOWER</name>
  <Polygon><extrude>1</extrude><altitudeMode>relativeToGround</altitudeMode>
    <outerBoundaryIs><LinearRing>
      <coordinates>37.6100,55.7600,90 37.6103,55.7600,90 37.6103,55.7602,90 37.6100,55.7600,90</coordinates>
    </LinearRing></outerBoundaryIs>
  </Polygon>
</Placemark>

Препятствие становится бесполётной зоной obs-N от земли до высоты препятствия с запасом. Линия (например, ЛЭП) превращается в полосу ±30 м. Высоты над уровнем моря пересчитываются по рельефу. Препятствия ниже 60 м не импортируются.

Чего нет в файлах и что подставляется

Данные Значение после импорта (правится в интерфейсе)
Площадка у края района; высота — из модели рельефа стенда; оборот 10 мин
Парк два Gemini и один 801; на большом районе (охват больше 8 км или площадь больше 10 км²) — ещё до трёх бортов 201
Разрешённое пространство охват района + 1,5 км
Ветер 0 (при включённом прогнозе подставится прогноз)
Критерий время работ, резерв 20 %, разворот с выходом на ЛЗП, буфер 25 м, лимит 300 с

На карточке «Импорт проекта» есть флажок, которым сценарий помечается как построенный из данных организаторов (POST /api/scenarios?geoscan_data=true); такие сценарии отмечены в списке.

GeoJSON и KML в свободной форме

GeoJSON: FeatureCollection, одиночный Feature или голая геометрия. Роль объекта задаётся свойством role (или kind): без роли или survey — участок; no_fly, nfz, restricted, zone — зона (высоты в altitude_min_m / altitude_max_m); allowed, airspace — граница разрешённого пространства; Point — площадка (name, elevation_m). Для участков читаются name, survey_type, gsd_cm.

{ "type": "FeatureCollection", "features": [
  { "type": "Feature", "properties": { "name": "Поле 1", "survey_type": "rgb", "gsd_cm": 3 },
    "geometry": { "type": "Polygon", "coordinates": [[[37.62,55.76],[37.636,55.76],[37.636,55.769],[37.62,55.769],[37.62,55.76]]] } },
  { "type": "Feature", "properties": { "role": "no_fly", "name": "Зона 1", "altitude_min_m": 0, "altitude_max_m": 500 },
    "geometry": { "type": "Polygon", "coordinates": [[[37.626,55.763],[37.63,55.763],[37.63,55.766],[37.626,55.766],[37.626,55.763]]] } },
  { "type": "Feature", "properties": { "name": "ВПП-1", "elevation_m": 145 },
    "geometry": { "type": "Point", "coordinates": [37.62, 55.76] } }
]}

KML: пространства имён, MultiGeometry, вырезы, 3D-координаты, ExtendedData и TimeSpan. KMZ распаковывается автоматически.

В интерфейсе есть готовые примеры: 01-field.geojson, 02-two-fields-nfz.geojson, 03-field.kml, 04-scenario-mixed.json.

Выгрузка KML

Один файл на весь план. Высоты — над землёй (relativeToGround), координаты — 7 знаков.

<kml><Document>
  <name>ПЗ s01-simple</name>
  <description>makespan …, налёт …, галсов …, снимков …</description>
  <Style id="phase-survey"> …                       стили этапов
  <Folder> scenario s01-simple                      слои сценария
     Placemark "t1 area"            участок         (ExtendedData layer=survey_area)
     Placemark "allowed airspace"   граница
     Placemark "NFZ z1"             зона + TimeSpan
     Placemark "vpp-1"              площадка; "vpp-1 runway" — полоса
  <Folder> gemini-01                                по борту
     <Folder> Вылет 1
        Placemark "launch vpp-1" / "landing vpp-1"
        Placemark "survey 3"  ExtendedData: uav_id, sortie, phase, seq, altitude_m,
                              altitude_frame=AGL, duration_s, start_s, task_id,
                              transect_id, speed_ms
          <LineString><altitudeMode>relativeToGround</altitudeMode>
            <coordinates>lon,lat,alt …</coordinates>

Этапы: takeoff, transit, survey, turn, return, approach, landing. Файл открывается в Google Earth и QGIS и загружается обратно через «Импорт проекта»: участки, зоны, площадки и полосы встанут на место, этапы полёта импорт пропустит.

Выгрузка GeoJSON

FeatureCollection: сначала слои сценария (properties.layer: survey_area, allowed_airspace, no_fly_zone, launch_site, runway), затем точки взлёта и посадки каждого вылета, затем этапы как LineString с высотой третьей координатой.

{ "type": "FeatureCollection", "features": [
  { "type": "Feature",
    "properties": { "layer": "survey_area", "task_id": "t1", "task_name": "Прямоугольник 1×1 км",
                    "survey_type": "rgb", "gsd_cm": 3 },
    "geometry": { "type": "Polygon", "coordinates": [[[37.62,55.76],[37.636,55.76],[37.636,55.769],[37.62,55.769],[37.62,55.76]]] } },
  { "type": "Feature",
    "properties": { "site_role": "launch", "site_id": "vpp-1", "uav_id": "gemini-01", "sortie": 1 },
    "geometry": { "type": "Point", "coordinates": [37.62, 55.76, 0.0] } },
  { "type": "Feature",
    "properties": { "scenario_id": "s01-simple", "uav_id": "gemini-01", "sortie": 1,
                    "launch_site": "vpp-1", "landing_site": "vpp-1", "phase": "survey", "seq": 3,
                    "altitude_m": 153.2, "altitude_frame": "AGL", "duration_s": 71.0, "start_s": 120.0,
                    "task_id": "t1", "transect_id": "t1-0", "speed_ms": 14.0 },
    "geometry": { "type": "LineString", "coordinates": [[37.6206,55.76,153.2],[37.6206,55.769,153.2]] } }
]}

Свойства слоя зоны: zone_id, zone_name, zone_kind, lower_m, lower_ref, upper_m, upper_ref (GND, AGL, AMSL, FL, UNL), valid_from, valid_to, exceptions, source. Свойства слоя полосы: site_id, width_m, length_m, heading_deg, surface.

QGC .plan и путевые точки

  • QGC .plan — по файлу на борт (?uav=<id>): JSON QGroundControl {fileType: "Plan", mission: {cruiseSpeed, hoverSpeed, items[]}}, у точек — координаты, высота и этап.
  • Путевые точки — по файлу на борт, текст с заголовком # lon lat alt_m phase seq uav_id speed_ms, значения через пробел; включены этапы галса, перелёта, разворота, возврата и захода. Кнопка «CSV» на экране «Экспорт» сохраняет именно этот файл.

QGroundControl

Серверный .plan не содержит кодов команд MAVLink (command, frame, params), поэтому перед загрузкой в QGroundControl его нужно проверить. Основные форматы для передачи на наземную станцию — KML и GeoJSON.

JSON-план

Документ плана отдаёт GET /api/plans/{id} в поле plan, когда расчёт завершён.

Поле Смысл
solver критерий, λ, лимит, зерно, время решения, статус (optimal, feasible, infeasible, timeout), причина остановки
metrics makespan_s, total_flight_time_s, coverage_frac, transects_total, turns_total, turn_time_frac, photos_total, uavs_used, sorties_total, total_distance_m, violations
missions[] полётное задание на борт: uav_id, модель, нагрузка, sorties[]
sorties[] вылет: seq, площадки взлёта и посадки, duration_s, energy_used_frac, start_s, phases[]
phases[] этап: тип, duration_s, geometry (LineString), altitude_m или altitudes_m[], speed_ms, task_id, transect_id, интервал съёмки, число снимков, режим и радиус разворота
unassigned[] нераспределённые галсы с кодом причины
warnings[] предупреждения с кодом и, где есть, предложением исправления (например, ALT_ABOVE_150M)
decisions[] протокол решений: угол, геометрия, парк, загрузка, разведение
geometry[] по участку: высота, GSD, отпечаток кадра, шаг и угол галсов, интервал съёмки, список галсов

Коды причин unassigned[].reason:

Код Что значит
no_compatible_payload нет борта с подходящей установленной нагрузкой
endurance_exceeded галс или цепочка не укладываются в запас хода с резервом
outside_allowed_airspace галс вне разрешённого пространства
inside_no_fly_zone галс в бесполётной зоне
wind_above_limit ветер выше предела борта
time_window_missed не уложились во временное окно участка
solver_time_limit расчёт остановлен по лимиту времени

Отчёт валидатора

POST /api/validate возвращает {"report": …}:

{ "report": {
  "scenario_id": "s01-simple", "criterion": "makespan", "admitted": true,
  "metrics": { "makespan_s": 1657.5, "total_flight_time_s": 1657.5, "coverage_frac": 1.0, "…": "…" },
  "declared_metrics": { "…": "…" },
  "checks": [
    { "id": "G1-05", "group": "airspace", "title": "…", "status": "warn",
      "value": 153.2, "limit": 150.0, "units": "m", "message": "…",
      "objects": ["t1"], "violation": null, "violation_amount": 0, "details": {} }
  ],
  "unassigned": [], "warnings": [], "notes": [], "missing_checks": [{ "id": "G7-02", "reason": "…" }]
}}

Поля и смысл проверок — Проверки валидатора.

Отчёт PDF

Печатный отчёт формирует браузер (листы A4, «Сохранить как PDF» в диалоге печати): сводный лист, схема района, листы бортов и вылетов с этапами и галсами, приложение с предупреждениями, нераспределёнными галсами, итогом валидатора и протоколом решений. См. Интерфейс.