QGroundControl 航点规划文件(.plan)格式完全指南:Plan JSON 结构与源码级解析
QGroundControl 航点规划文件(.plan)格式完全指南:Plan JSON 结构与源码级解析
QGroundControl(QGC)使用 .plan 文件保存完整的航点规划数据:任务(Mission)航点、可选的地理围栏(GeoFence)与返航点(Rally Points)三大部分被统一封装在一个 JSON 文件中。本文以官方 Plan 文件格式文档(docs/en/qgc-dev-guide/file_formats/plan.md)为骨架,逐层解析 Plan 文件的顶层结构、任务项(SimpleItem/ComplexItem)、Survey/CorridorScan/StructureScan 复合任务、CameraCalc 相机参数、围栏与返航点的字段含义与取值约束,并对照 PlanMasterController.cc 的加载/保存实现与 PlanManager.h 的协议层,说明这些 JSON 字段在源码中如何被校验、解析和写出。读完本文,你将能够手写或程序化生成一个合法的 .plan 文件,也能准确理解 QGC 生成的规划文件中每个字段的语义。
1. Plan 文件顶层结构
Plan 文件使用 JSON 格式存储,由三个顶层对象组成:mission(任务,必填)、geoFence(地理围栏,可选)、rallyPoints(返航点,可选)。QGC 官方文档给出的“近最小”示例(plan 必须至少包含一个任务项)如下:
{
"fileType": "Plan",
"geoFence": {
"circles": [],
"polygons": [],
"version": 2
},
"groundStation": "QGroundControl",
"mission": {},
"rallyPoints": {
"points": [],
"version": 2
},
"version": 1
}
各顶层字段的含义如下表所示:
| Key | 描述 |
|---|---|
version |
文件格式版本,当前为 1。 |
fileType |
必须为 "Plan",用于标识文件类型。 |
groundStation |
创建该文件的 GCS 名称(示例为 QGroundControl)。 |
mission |
与飞行计划关联的任务对象(至少包含一个任务项)。 |
geoFence |
(可选)该计划的地理围栏信息。 |
rallyPoints |
(可选)该计划的返航/安全点信息。 |
需要特别注意的是:围栏与返航点不仅用于任务飞行过程中,在没有任务运行时同样生效(文档原话:“The plan fence and rally points are also used in modes when no mission is running”)。也就是说,即使 mission.items 为空,只要文件中声明了 geoFence 或 rallyPoints,对应的约束在无任务模式下依然会被使用。
1.1 源码中的文件头校验逻辑
在源码中,fileType 与 version 的取值是被硬编码并强制校验的。在 PlanMasterController.h 中定义了:
static constexpr int kPlanFileVersion = 1;
static constexpr const char* kPlanFileType = "Plan";
static constexpr const char* kJsonMissionObjectKey = "mission";
static constexpr const char* kJsonGeoFenceObjectKey = "geoFence";
static constexpr const char* kJsonRallyPointsObjectKey = "rallyPoints";
而 PlanMasterController.cc 中的 _loadPlanJson() 则是实际的解析入口:先用 JsonParsing::isJsonFile 校验文件是否为合法 JSON,再调用 JsonParsing::validateExternalQGCJsonFile(json, kPlanFileType, kPlanFileVersion, kPlanFileVersion, version, errorString) 强制校验 fileType == "Plan" 且 version 在允许范围内,最后通过 validateKeys 确认 mission、geoFence、rallyPoints 三个键均存在且为 JSON 对象——注意这里三个键都必须存在(即使内容是空对象),否则加载失败。
对应的写出路径是 saveToJson()(PlanMasterController.cc):调用 JsonParsing::saveQGCJsonFileHeader(planJson, kPlanFileType, kPlanFileVersion) 写入 fileType 与 version 文件头,随后依次保存 mission、geoFence、rallyPoints 三个对象。整个加载/保存流程还开放了插件钩子(preLoadFromJson / postLoadFromJson / preSaveToJson / postSaveToJson),允许 QGCCorePlugin 对 Plan 内容做预处理,例如 custom-example 中 CustomPlugin.cc 的自定义加载逻辑。
2. Mission 任务对象
mission 对象承载了任务的核心数据:飞行速度、机型信息、全局高度模式以及任务项列表。其结构如下:
"mission": {
"cruiseSpeed": 15,
"firmwareType": 12,
"globalPlanAltitudeMode": 1,
"hoverSpeed": 5,
"items": [
{
"AMSLAltAboveTerrain": null,
"Altitude": 50,
"AltitudeMode": 0,
"autoContinue": true,
"command": 22,
"doJumpId": 1,
"frame": 3,
"params": [
15,
0,
0,
null,
47.3985099,
8.5451002,
50
],
"type": "SimpleItem"
}
],
"plannedHomePosition": [
47.3977419,
8.545594,
487.989
],
"vehicleType": 2,
"version": 2
}
必填字段说明:
| Key | 描述 |
|---|---|
version |
Mission 对象格式版本,当前为 2。 |
firmwareType |
创建该任务时的飞控固件类型,取值为 MAV_AUTOPILOT 枚举值(示例 12 即 MAV_AUTOPILOT_PX4)。 |
globalPlanAltitudeMode |
全局任务级高度模式,作用于未显式指定 AltitudeMode 的任务项。 |
vehicleType |
创建该任务时的机型类型,取值为 MAV_TYPE 枚举值(示例 2 即 MAV_TYPE_QUADROTOR)。 |
cruiseSpeed |
固定翼或 VTOL 机型在两个航点之间飞行的默认前向速度(m/s)。 |
hoverSpeed |
多旋翼机型的默认前向速度(m/s)。 |
items |
任务项对象列表,可混合包含 SimpleItem 与 ComplexItem,至少包含一个任务项。 |
plannedHomePosition |
规划起飞点(Home)位置,用于无飞机连接时在地图上显示并参与任务规划。数组元素依次为纬度、经度、AMSL 海拔高度。 |
从源码角度看,MissionController.cc 的 save() 完整印证了这些字段的写出顺序:version 使用 _missionFileVersion;plannedHomePosition 通过 GeoJsonHelper::saveGeoCoordinate() 序列化(会带上高度,writeAltitude = true);firmwareType、vehicleType、cruiseSpeed、hoverSpeed 直接取自当前控制器关联的 Vehicle(_controllerVehicle->firmwareType() 等);globalPlanAltitudeMode 则来自 _globalAltFrame。随后遍历 _visualItems 逐个调用 visualItem->save() 生成 items 数组。值得留意的是“结束任务动作”(End Mission Action)是作为最后一个隐藏任务项追加进 items 的(见 save() 中 settingsItem 的特殊处理分支)。
3. SimpleItem:简单任务项
一个 SimpleItem 对应一条 MAVLink MISSION_ITEM 命令,是最基本的任务元素(航点、速度设置、悬停、相机命令等都属于此类)。完整示例:
{
"AMSLAltAboveTerrain": null,
"Altitude": 50,
"AltitudeMode": 0,
"autoContinue": true,
"command": 22,
"doJumpId": 1,
"frame": 3,
"params": [
15,
0,
0,
null,
47.3985099,
8.5451002,
50
],
"type": "SimpleItem"
}
字段映射:
| Key | 描述 |
|---|---|
type |
固定为 SimpleItem。 |
AMSLAltAboveTerrain |
展示给用户的“高于地形”AMSL 高度值。 |
Altitude |
任务项高度。 |
AltitudeMode |
高度模式(相对/绝对等)。 |
autoContinue |
对应 MISSION_ITEM.autoContinue,控制当前项完成后是否自动继续下一项。 |
command |
该任务项的命令(MAV_CMD),对应 MISSION_ITEM.command。 |
doJumpId |
当前任务项在 DO_JUMP 命令中作为跳转目标的编号,从 1 开始自动编号。 |
frame |
MAV_FRAME 坐标参考系,对应 MISSION_ITEM.frame。 |
params |
对应 MISSION_ITEM 的 param1、param2、param3、param4、x、y、z,具体语义取决于 command 对应的 MAV_CMD。 |
以上面示例为例做一次实战解码:command: 22 即 MAV_CMD_NAV_WAYPOINT(飞向航点),frame: 3 即 MAV_FRAME_GLOBAL_RELATIVE_ALT(相对起飞点高度的全局坐标)。此时 params 的语义为:param1=15(悬停停留时间,秒)、param2=0(接受半径,米)、param3=0(通过半径,米)、param4=null(航向角,未指定)、x/y/z 依次为纬度 47.3985099、经度 8.5451002、高度 50(米,相对高度)。
4. ComplexItem:复合任务项
复合任务项(ComplexItem)是多个 MISSION_ITEM 的高层封装,对用户表现为一个可编辑的整体实体,保存时由 QGC 展开为具体命令序列。目前共有三种类型:
- Survey(正射测绘/航拍扫描)
- Corridor Scan(走廊扫描)
- Structure Scan(结构扫描)
4.1 Survey 复合任务项
Survey(测绘扫描)用于按航带(transect)对指定多边形区域进行覆盖拍摄。对象定义:
{
"TransectStyleComplexItem": {
...
},
"angle": 0,
"complexItemType": "survey",
"entryLocation": 0,
"flyAlternateTransects": false,
"polygon": [
[
-37.75170619863631,
144.98414811224316
],
...
[
-37.75170619863631,
144.99457681259048
]
],
"type": "ComplexItem",
"version": 4
}
| Key | 描述 |
|---|---|
version |
该 survey 定义的版本号(文档中注记为 3,示例文件为 4,以实际导出为准)。 |
type |
固定为 ComplexItem。 |
complexItemType |
固定为 survey(小写)。 |
TransectStyleComplexItem |
Survey 与 CorridorScan 共用的基础定义(见第 6 节)。 |
angle |
航带路径的方向角(度)。 |
entryLocation |
进入点位置(文档未详细展开,QGC 生成文件中通常为 0)。 |
flyAlternateTransects |
若为 true,飞行器将先隔行飞行所有奇数/偶数航带,再回头补飞被跳过的航带;用于转弯半径过小、固定翼无法完成急转的场景。 |
polygon |
多边形测绘区域顶点数组,每个顶点为 [纬度, 经度] 二元组。 |
4.2 CorridorScan 复合任务项
CorridorScan(走廊扫描)沿一条折线路径生成覆盖航带,常用于河流、道路、输电线巡检。对象定义:
{
"CorridorWidth": 50,
"EntryPoint": 0,
"TransectStyleComplexItem": {
...
},
"complexItemType": "CorridorScan",
"polyline": [
[
-37.75234887156983,
144.9893624624168
],
...
[
-37.75491914850321,
144.9893624624168
]
],
"type": "ComplexItem",
"version": 2
}
| Key | 描述 |
|---|---|
version |
该 CorridorScan 定义的版本号(文档中注记为 3,示例为 2)。 |
type |
固定为 ComplexItem。 |
complexItemType |
固定为 CorridorScan。 |
CorridorWidth |
走廊扫描宽度(米),决定航带向折线两侧展开的覆盖范围(文档未详细展开,结合 CorridorScanComplexItem.h 的语义可推断)。 |
EntryPoint |
扫描入口点(文档未详细展开)。 |
TransectStyleComplexItem |
与 Survey 共用的基础定义(见第 6 节)。 |
polyline |
走廊中心线折线点数组,每个点同样为 [纬度, 经度] 二元组。 |
4.3 StructureScan 复合任务项
StructureScan(结构扫描)针对单个建筑/结构物,围绕其多边形底面按层(Layers)从下至上生成环绕拍摄路径。对象定义:
{
"Altitude": 50,
"CameraCalc": {
"AdjustedFootprintFrontal": 25,
"AdjustedFootprintSide": 25,
"CameraName": "Manual (no camera specs)",
"DistanceToSurface": 10,
"DistanceToSurfaceRelative": true,
"version": 1
},
"Layers": 1,
"StructureHeight": 25,
"altitudeRelative": true,
"complexItemType": "StructureScan",
"polygon": [
[
-37.753184359536355,
144.98879374063998
],
...
[
-37.75408368012594,
144.98879374063998
]
],
"type": "ComplexItem",
"version": 2
}
| Key | 描述 |
|---|---|
version |
该 StructureScan 定义的版本号,当前为 2。 |
type |
固定为 ComplexItem。 |
complexItemType |
固定为 StructureScan。 |
Altitude |
扫描飞行高度(米)。 |
CameraCalc |
相机参数(见第 7 节),决定相邻拍摄点的间隔与重叠率。 |
Layers |
扫描层数:结构物越高,需要的环绕层越多。 |
StructureHeight |
结构物高度(米),用于计算各层高度。 |
altitudeRelative |
true:高度相对起飞点(home);false:高度为 AMSL 绝对高度。 |
polygon |
结构物底面多边形顶点数组([纬度, 经度] 二元组列表)。 |
5. TransectStyleComplexItem:Survey 与 CorridorScan 的公共基定义
TransectStyleComplexItem 承载了 Survey 与 CorridorScan 两类航带类任务的全部共性配置(相机、地形跟随、转弯参数等):
"TransectStyleComplexItem": {
"CameraCalc": {
...
},
"CameraTriggerInTurnAround": true,
"FollowTerrain": false,
"HoverAndCapture": false,
"Items": [
...
],
"Refly90Degrees": false,
"TurnAroundDistance": 10,
"VisualTransectPoints": [
[
-37.75161626657736,
144.98414811224316
],
...
[
-37.75565155437309,
144.99438539496475
]
],
"version": 1
}
| Key | 描述 |
|---|---|
version |
该定义的版本号,当前为 1。 |
CameraCalc |
相机参数定义(见第 7 节)。 |
CameraTriggerInTurnAround |
(布尔)是否在航带转弯区域也触发相机拍照。 |
FollowTerrain |
(布尔)是否跟随地形起伏调整高度。 |
HoverAndCapture |
(布尔)是否采用“悬停拍摄”模式(多旋翼在每拍点悬停后拍照)。 |
Items |
由复合任务展开生成的实际任务项列表(QGC 保存时写入)。 |
Refly90Degrees |
(布尔)是否以 +90° 交叉方向再扫描一遍(提高重叠率)。 |
TurnAroundDistance |
到达多边形边缘后、转向下一条航带之前,继续向前飞行的距离(米),即“转弯外扩距离”。 |
VisualTransectPoints |
可视化用的航带端点列表([纬度, 经度] 二元组数组)。 |
6. CameraCalc:相机参数
CameraCalc 用于 Survey、Corridor 或 Structure Scan,描述相机型号、镜头参数与覆盖重叠率,QGC 据此计算航带间距与拍照间隔。完整示例:
"CameraCalc": {
"AdjustedFootprintFrontal": 272.4,
"AdjustedFootprintSide": 409.2,
"CameraName": "Sony ILCE-QX1",
"DistanceToSurface": 940.6896551724138,
"DistanceToSurfaceRelative": true,
"FixedOrientation": false,
"FocalLength": 16,
"FrontalOverlap": 70,
"ImageDensity": 25,
"ImageHeight": 3632,
"ImageWidth": 5456,
"Landscape": true,
"MinTriggerInterval": 0,
"SensorHeight": 15.4,
"SensorWidth": 23.2,
"SideOverlap": 70,
"ValueSetIsDistance": false,
"version": 1
}
| Key | 描述 |
|---|---|
version |
该 CameraCalc 定义的版本号,当前为 1。 |
AdjustedFootprintFrontal |
调整后的前向(沿航线方向)地面覆盖尺寸。 |
AdjustedFootprintSide |
调整后的侧向(垂直航线方向)地面覆盖尺寸。 |
DistanceToSurface |
相机到地面/目标表面的距离。 |
DistanceToSurfaceRelative |
距离是否为相对值。 |
CameraName |
相机名称,必须匹配 QGC 已知的相机,或使用 Manual (no camera specs)(手动配置)、Custom Camera(自定义相机)。对于 “Manual” 模式,其后各字段不写出/不使用。 |
FixedOrientation |
(布尔)相机是否固定朝向。 |
FocalLength |
镜头焦距(毫米)。 |
FrontalOverlap |
前向(航向方向)图像重叠率百分比。 |
ImageDensity |
图像密度(决定航带间距的附加参数)。 |
ImageHeight |
图像高度(像素)。 |
ImageWidth |
图像宽度(像素)。 |
Landscape |
true:相机横置安装;false:相机竖置安装。 |
MinTriggerInterval |
两次拍照之间的最小时间间隔(秒),用于规避快门过速。 |
SensorHeight |
传感器高度(毫米)。 |
SensorWidth |
传感器宽度(毫米)。 |
SideOverlap |
侧向图像重叠率百分比。 |
ValueSetIsDistance |
(布尔)当前参数集是以“距离”还是“重叠率”为核心设置方式。 |
其中 SensorWidth/SensorHeight/FocalLength/ImageWidth/ImageHeight 共同决定了单张照片的地面覆盖范围,FrontalOverlap/SideOverlap 则决定相邻航带与相邻拍照点的间距,最终换算为 AdjustedFootprintFrontal/AdjustedFootprintSide 与航带布局。
7. GeoFence 地理围栏
地理围栏信息是可选的,一个 Plan 可包含任意数量的围栏,由多边形(polygons)与圆形(circles)两类构成。最小定义:
"geoFence": {
"circles": [],
"polygons": [],
"version": 2
}
| Key | 描述 |
|---|---|
version |
围栏部分格式版本号,文档版本为 2。 |
circles |
圆形围栏定义列表(多个以逗号分隔)。 |
polygons |
多边形围栏定义列表(多个以逗号分隔)。 |
7.1 Circle 圆形围栏
每个圆形围栏一个条目,定义圆心、半径以及该围栏是否启用:
{
"circle": {
"center": [47.39756763610029, 8.544649762407738],
"radius": 319.85
},
"inclusion": true,
"version": 1
}
| Key | 描述 |
|---|---|
version |
圆形围栏条目格式版本,文档版本为 1。 |
circle |
圆定义:center 为 [纬度, 经度],radius 为半径。 |
inclusion |
该围栏是否启用(true 启用,false 禁用)。 |
7.2 Polygon 多边形围栏
每个多边形围栏一个条目,顶点按顺时针方向排列(必须包围一个闭合区域):
{
"inclusion": true,
"polygon": [
[
47.39807773798406,
8.543834631785785
],
[
47.39983519888905,
8.550024648373267
],
[
47.39641100087146,
8.54499282423751
],
[
47.395590322265186,
8.539435808992085
]
],
"version": 1
}
| Key | 描述 |
|---|---|
version |
多边形围栏条目格式版本,文档版本为 2。 |
polygon |
多边形顶点列表,每个顶点包含纬度与经度,顶点按顺时针顺序排列。 |
inclusion |
该围栏是否启用(true 启用,false 禁用)。 |
围栏数据在源码中由 GeoFenceManager 与 GeoFenceController 负责持久化与编辑;圆与多边形两类条目的加载校验同样经由 PlanMasterController::_loadPlanJson() 统一入口(PlanMasterController.cc)。
8. Rally Points 返航点
返航点信息可选,Plan 可包含任意数量的返航点,每个点由纬度、经度与相对起飞点的高度(above home position)构成。两个点的示例:
"rallyPoints": {
"points": [
[
47.39760401,
8.5509154,
50
],
[
47.39902017,
8.54263274,
50
]
],
"version": 2
}
| Key | 描述 |
|---|---|
version |
返航点部分格式版本,文档版本为 2。 |
points |
返航点列表;每个点为 [纬度, 经度, 高度] 三元组,高度为相对起飞点(home)的高度。 |
返航点数据由 RallyPointManager 与 RallyPointController 管理,与任务、围栏一样统一走 Plan 文件的加载/保存链路。
9. Plan 文件的加载与写出流程(源码级)
把前面的 JSON 结构与 QGC 源码串起来,完整的 Plan 文件生命周期如下:
- 选择文件:用户通过文件对话框选择
.plan文件;PlanMasterController::loadFromFile()(PlanMasterController.cc)先按扩展名分流:若扩展名是.waypoints(waypointsFileExtension)或.txt,则走_missionController.loadTextFile()解析旧式纯文本航点文件;否则走_loadPlanJson()按 JSON 解析。 - JSON 校验:
_loadPlanJson()依次完成 JSON 语法检查、文件头校验(fileType/version)、顶层三键类型校验,再分别把mission/geoFence/rallyPoints三个子对象交给MissionController、GeoFenceController、RallyPointController的load()逐项解析。 - 加载成功:更新
currentPlanFile路径,并将 dirty 状态置为“需上传、无需保存”。 - 写出:
saveToJson()组装文件头与三个子对象;saveToFile()自动补.plan扩展名(fileExtension())后以 UTF-8 文本写盘,并校验写入字节数是否完整(PlanMasterController.cc)。此外还支持saveToKml()将任务导出为 KML(KMLPlanDomDocument)。
在协议层,任务项最终通过 PlanManager 与飞控交互——它是 Mission、GeoFence、Rally Point 三类管理器(MAV_MISSION_TYPE 区分)的公共基类,基于 mavlink v2 mission 协议实现 loadFromVehicle()(从飞控读回)、writeMissionItems()(写入飞控)与 removeAll(),并内置 ACK 超时与重试机制(_ackTimeoutMilliseconds = 1500ms、_retryTimeoutMilliseconds = 250ms、_maxRetryCount = 5),错误码覆盖 Ack 超时、协议错误、条目越界、条目不匹配、车辆 ACK 错误等场景(PlanManager.h)。也就是说:.plan 文件是磁盘上的持久化格式,而飞控与地面站之间的任务同步则走 MAVLink mission 协议,两者通过 MissionItem 的字段(command/frame/params)天然对应。
10. 版本与兼容性说明
- Plan 文件顶层
version: 1、Mission 对象version: 2、围栏version: 2、返航点version: 2是各节当前的文档版本;复合任务项内部还有各自的小版本(TransectStyleComplexItem为 1,CameraCalc为 1)。QGC 的JsonParsing::validateExternalQGCJsonFile会校验顶层版本,读取旧版本文件时按向后兼容策略处理。 fileType必须严格为"Plan";mission、geoFence、rallyPoints三个顶层键在文件中都必须存在(可空)。- Survey 示例中的
"complexItemType": "survey"为小写,而 CorridorScan 与 StructureScan 使用驼峰命名(CorridorScan/StructureScan),手写文件时需注意区分。 - 本文中的枚举数值(如
command: 22、frame: 3、firmwareType: 12、vehicleType: 2)均以 MAVLink 标准枚举定义为准,具体语义可对照 MAVLink 协议文档中的 MAV_CMD / MAV_FRAME / MAV_AUTOPILOT / MAV_TYPE 枚举。 - 测试工程中保留了完整的使用范例:例如 MissionControllerTest.cc 通过
saveToFile()写出VTOLTakeoff.plan再loadFromFile()回读验证保存-加载闭环,并通过loadFromFile(":/unittest/SectionTest.plan")加载内置测试计划;PlanMasterControllerTest.cc 则验证了加载/保存后的 dirty 状态语义。需要自定义格式联调或自研 GCS 对接时,这些测试用例是很好的参考基准。
实用建议:若需程序化生成 Plan 文件,最稳妥的方式是先用 QGC 规划一个包含航点、测绘、围栏与返航点的完整计划并导出,以此为模板修改坐标与参数;修改时注意保持各节 version 不变、params 数量与 command 的 MAV_CMD 语义一致、多边形围栏顶点保持顺时针,即可获得飞控可接受且 QGC 可正常回读的合法文件。