Работа с файлами¶
Все файлы, которые Аэрозвено принимает и отдаёт. Примеры ниже синтетические: они построены на демо-сценариях продукта и не содержат данных реальных районов работ.
Сводка форматов¶
| Направление | Формат | Где |
|---|---|---|
| Вход | 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» в диалоге печати): сводный лист, схема района, листы бортов и вылетов с этапами и галсами, приложение с предупреждениями, нераспределёнными галсами, итогом валидатора и протоколом решений. См. Интерфейс.