QGC无人机任务与航线开发
5.0 总体架构
QGC 的任务与航线子系统采用 分层 MVC + 协议适配 架构,将「用户可见的航线对象」与「飞控可执行的 MAVLink 指令序列」严格分离。
┌─────────────────────────────────────────────────────────────┐
│ View 层:PlanView.qml + *Editor.qml + *MapVisual.qml │
│ (QML,地图交互、属性编辑、上传按钮) │
└──────────────────────────┬──────────────────────────────────┘
│ Q_PROPERTY / Q_INVOKABLE / 信号槽
┌──────────────────────────▼──────────────────────────────────┐
│ Controller 层:PlanMasterController → MissionController │
│ (任务编辑、统计、序列化、协调围栏/集结点) │
└──────────────────────────┬──────────────────────────────────┘
│ appendMissionItems / writeMissionItems
┌──────────────────────────▼──────────────────────────────────┐
│ Model 层:VisualMissionItem → MissionItem │
│ (可视化任务项 → MAVLink MISSION_ITEM) │
└──────────────────────────┬──────────────────────────────────┘
│ MAVLink Mission Protocol v2
┌──────────────────────────▼──────────────────────────────────┐
│ Protocol 层:PlanManager / MissionManager │
│ (MISSION_COUNT / MISSION_ITEM_INT / CLEAR_ALL) │
└──────────────────────────┬──────────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────────┐
│ Vehicle 层:Vehicle::missionManager() │
└─────────────────────────────────────────────────────────────┘
涉及的主要设计模式:
| 模式 | 应用场景 |
|---|---|
| MVC | QML View ↔ C++ Controller ↔ MissionItem Model |
| 组合模式 | VisualMissionItem::appendMissionItems() 展开为多条 MAVLink 指令 |
| 模板方法 | TransectStyleComplexItem::_rebuildTransects() 由子类实现 Phase1 |
| 策略模式 | FirmwarePlugin 按 PX4/APM 差异处理 Home、续飞、起飞 |
| 观察者(信号槽) | dirtyChanged、sendComplete、progressPct、resumeMissionReady |
| Fact 系统 | 任务参数、高度、速度等统一绑定 UI |
| 工厂/注册 | MissionCommandTree 按固件/机型过滤可用 MAV 命令 |
涉及语法与技术:
- C++11:
QObject继承、Q_PROPERTY、Q_ENUM、Q_INVOKABLE - Qt 元对象:
signals/slots、connect()、qobject_cast - QML:
Loader、Repeater、MouseArea、属性绑定 - JSON:
QJsonDocument/QJsonObject序列化.plan/.mission - XML/KML:
QDomDocument解析与导出 - 地理计算:
QGeoCoordinate+ GeographicLib(UTM/MGRS)
5.1 航点任务、悬停、绕圈任务逻辑
5.1.1 类层次与职责划分
QGC 不把「航点」「悬停」「绕圈」做成三个独立顶层类,而是用 VisualMissionItem 继承树 统一抽象,再按 MAV_CMD 区分行为。
MissionItem(MAVLink 原子指令,Fact 封装 7 参数)
↑ 被引用/展开
VisualMissionItem(地图/UI 抽象:坐标、距离、方位、dirty)
├── MissionSettingsItem(任务全局:Home、速度、结束动作)
├── SimpleMissionItem(单点任务:航点/Loiter/Land/Takeoff…)
│ └── TakeoffMissionItem(起飞专用)
└── ComplexMissionItem(复杂模式:Survey/Corridor/StructureScan…)
├── SurveyComplexItem
├── CorridorScanComplexItem
├── StructureScanComplexItem
└── FixedWingLandingComplexItem(含 Loiter 进近)
MissionItem 是 MAVLink 任务协议的最小单元,每个命令用 Fact 存储参数:
// Represents a Mavlink mission command.
class MissionItem : public QObject
VisualMissionItem 是 Plan 视图与 Fly 视图共享的可视化基类,暴露 coordinate、exitCoordinate、commandName 等 Q_PROPERTY,供 QML 绑定。
SimpleMissionItem 代表用户地图上点击的一个「简单航点」。其内部持有一个 MissionItem,并附加 CameraSection(相机动作段)、SpeedSection(速度段),上传时会展开成多条指令:
void SimpleMissionItem::appendMissionItems(QList<MissionItem*>& items, QObject* missionItemParent)
{
int seqNum = sequenceNumber();
items.append(new MissionItem(missionItem(), missionItemParent));
seqNum++;
_cameraSection->appendSectionItems(items, missionItemParent, seqNum);
_speedSection->appendSectionItems(items, missionItemParent, seqNum);
}
这是典型的 组合模式:UI 上 1 个航点 → 飞控上 N 条 MISSION_ITEM。
5.1.2 航点(Waypoint)任务逻辑
创建流程(Plan 视图):
- 用户在
PlanView.qml地图MouseArea点击 → 调用MissionController.insertSimpleMissionItem(coordinate, command) MissionController::_insertSimpleMissionItemWorker()创建SimpleMissionItem,设置序列号、坐标、命令- 若新点需要高度,调用
_findPreviousAltitude()继承前一点高度模式 _recalcAllWithCoordinate()重算整条航线距离、时间、包围盒
VisualMissionItem* MissionController::_insertSimpleMissionItemWorker(QGeoCoordinate coordinate, MAV_CMD command, int visualItemIndex, bool makeCurrentItem)
{
int sequenceNumber = _nextSequenceNumber();
SimpleMissionItem * newItem = new SimpleMissionItem(_controllerVehicle, _flyView, false /* forLoad */, this);
newItem->setSequenceNumber(sequenceNumber);
newItem->setCoordinate(coordinate);
newItem->setCommand(command);
默认航点命令为 MAV_CMD_NAV_WAYPOINT。命令元数据(友好名称、参数标签、是否含坐标)来自 JSON 配置 MavCmdInfoCommon.json,由 MissionCommandTree 加载并按固件/机型过滤。
上传展开:
MissionController::_convertToMissionItems() 遍历所有 VisualMissionItem,逐个调用 appendMissionItems(),最后由 MissionSettingsItem::addMissionEndAction() 追加任务结束动作(RTL/Land/Continue 等):
for (int i=0; i<visualMissionItems->count(); i++) {
VisualMissionItem* visualItem = qobject_cast<VisualMissionItem*>(visualMissionItems->get(i));
lastSeqNum = visualItem->lastSequenceNumber();
visualItem->appendMissionItems(rgMissionItems, missionItemParent);
}
MissionSettingsItem* settingsItem = visualMissionItems->value<MissionSettingsItem*>(0);
if (settingsItem) {
endActionSet = settingsItem->addMissionEndAction(rgMissionItems, lastSeqNum + 1, missionItemParent);
}
5.1.3 绕圈(Loiter)任务逻辑
QGC 没有 名为 CircleMissionItem 的独立类。绕圈/盘旋通过 SimpleMissionItem + MAV Loiter 命令族 实现:
| MAV 命令 | ID | 含义 |
|---|---|---|
MAV_CMD_NAV_LOITER_UNLIM | 17 | 无限期盘旋 |
MAV_CMD_NAV_LOITER_TURNS | 18 | 指定圈数 |
MAV_CMD_NAV_LOITER_TIME | 19 | 指定时间 |
MAV_CMD_NAV_LOITER_TO_ALT | — | 盘旋至指定高度(固定翼常用) |
命令定义示例(MavCmdInfoCommon.json):
"id": 17,
"rawName": "MAV_CMD_NAV_LOITER_UNLIM",
"friendlyName": "Loiter",
"description": "Travel to a position and Loiter around the specified radius indefinitely.",
"specifiesCoordinate": true,
- param3:盘旋半径(米)
- param4:期望航向(度,NaN 表示不改变)
- 坐标:盘旋中心点(lat/lon/alt)
用户在 Plan 视图选择「Loiter」命令类型后,SimpleMissionItem 的 command Fact 变为对应 MAV_CMD,地图可视化由 SimpleItemMapVisual.qml 渲染。
固定翼降落模式中,FixedWingLandingComplexItem 自动生成包含 Loiter 进近点的复合降落航线,属于 ComplexMissionItem 范畴。
运行时引导绕圈(非任务编辑)在 Vehicle.cc 中通过 MAV_CMD_DO_ORBIT 实现,属于 Guided 模式,不走 MissionController。
5.1.4 悬停(Hover)任务逻辑
「悬停」在 QGC 中有两层含义:
(1)多旋翼/VTOL 飞行时间统计中的 Hover
MissionController 维护 MissionFlightStatus_t 结构体,区分 hoverDistance/hoverTime 与 cruiseDistance/cruiseTime。根据机型与 VTOL 状态选择 vehicleSpeed(悬停速度或巡航速度),用于任务总时长与电量估算。相关 Q_PROPERTY 绑定 Fly 视图任务进度面板。
(2)测绘任务中的「悬停拍照」(hoverAndCapture)
TransectStyleComplexItem(条带扫描基类)提供 hoverAndCapture Fact。开启后,Survey 生成航点时插入 CoordTypeInteriorHoverTrigger 类型坐标点,飞机在条带内部悬停触发拍照,而非边飞边拍。
条带重建流程(模板方法):
_rebuildTransects()
→ _rebuildTransectsPhase1() // 子类实现:Survey/Corridor
→ 多边形顶点 convertGeoToNed
→ 平面内计算平行扫描线
→ convertNedToGeo 转回经纬度
→ 生成 visualTransectPoints
5.1.5 复杂任务(Survey / Corridor / Structure Scan)
ComplexMissionItem 继承 VisualMissionItem,额外提供:
complexDistance:复杂区域覆盖距离load(QJsonObject)/save(QJsonObject):JSON 持久化loadPreset()/savePreset():预设存取(QSettings)isIncomplete:区域未画完时禁止上传
子类 SurveyComplexItem 典型流程:
- 用户在地图绘制
QGCMapPolygon - 设置相机参数(
CameraCalc)、间距、角度 _rebuildTransectsPhase1()在 NED 平面生成平行线appendMissionItems()输出大量MAV_CMD_NAV_WAYPOINT+ 相机触发指令
Plan 视图对应编辑器:SurveyItemEditor.qml、CorridorScanEditor.qml、StructureScanEditor.qml;地图可视化:SurveyMapVisual.qml、TransectStyleMapVisuals.qml。
5.1.6 MissionCommandTree:命令注册与过滤
MissionCommandTree(QGCToolbox 工具)从 JSON 加载全部 MAV 命令 UI 定义,按 _vehicle->firmwareType() 和 _vehicle->vehicleType() 过滤可用命令列表。Plan 视图添加航点时的命令下拉框、参数编辑器标签均来源于此。
这是 策略 + 注册表 模式:固件差异不在 View 层硬编码,而集中在 MissionCommandTree + FirmwarePlugin。
5.2 航线编辑、保存、导入导出
5.2.1 PlanMasterController:计划总控
PlanMasterController 是 Plan 视图的 Facade(外观)控制器,聚合三个子控制器:
| 子控制器 | 职责 |
|---|---|
MissionController | 航点任务 |
GeoFenceController | 地理围栏 |
RallyPointController | 集结/备降点 |
在 PlanView.qml 中实例化并启动:
PlanMasterController {
id: _planMasterController
Component.onCompleted: {
_planMasterController.start(false /* flyView */)
_missionController.setCurrentPlanViewSeqNum(0, true)
mainWindow.planMasterControllerPlan = _planMasterController
}
start(false) 表示 Plan 编辑模式(flyView=false),与 Fly 视图的任务监控模式相对。
dirty 传播: 三个子控制器的 dirtyChanged 信号连接到 PlanMasterController::dirtyChanged,任一子计划修改即标记整体未保存。
5.2.2 航线编辑交互
Plan 视图编辑链路:
- 地图打点:
PlanView.qml中FlightMap+MouseArea→insertSimpleMissionItem/insertTakeoffItem/insertLandItem - 属性编辑:选中航点 →
MissionItemEditor.qml/SimpleItemEditor.qml绑定VisualMissionItem的 Fact - 复杂任务创建:工具栏选择 Survey 等 →
insertComplexMissionItem或从 KML/SHP 导入区域 - 拖拽调整:
*MapVisual.qml中DragArea修改坐标 →setCoordinate→_recalcAll - 分割/删除:
splitSegment、removeVisualItem、moveVisualItem
MissionController 用 QmlObjectListModel* _visualItems 作为 QML 列表模型,注册为 Q_PROPERTY visualItems,Plan 视图 Repeater 直接绑定。
就绪检查(上传/保存前):
function checkReadyForSaveUpload(save) {
if (readyForSaveState() == VisualMissionItem.NotReadyForSaveData) {
waitingOnIncompleteDataMessage(save)
return false
} else if (readyForSaveState() == VisualMissionItem.NotReadyForSaveTerrain) {
waitingOnTerrainDataMessage(save)
return false
}
return true
}
NotReadyForSaveData:复杂任务区域未完成NotReadyForSaveTerrain:等待地形数据以计算正确 AMSL 高度
5.2.3 文件格式与扩展名
定义于 AppSettings:
| 扩展名 | 类型 | 内容 |
|---|---|---|
.plan | JSON | 完整计划(mission + geoFence + rallyPoints) |
.mission | JSON | 仅任务部分 |
.waypoints / .txt | 文本 | QGC/APM 兼容航点列表 |
.kml | KML | 航点路径导出(仅坐标序列) |
5.2.4 保存逻辑
内存 JSON 构建(PlanMasterController::saveToJson):
QJsonDocument PlanMasterController::saveToJson()
{
QJsonObject planJson;
qgcApp()->toolbox()->corePlugin()->preSaveToJson(this, planJson);
...
JsonHelper::saveQGCJsonFileHeader(planJson, kPlanFileType, kPlanFileVersion);
_missionController.save(missionJson);
_geoFenceController.save(fenceJson);
_rallyPointController.save(rallyJson);
planJson[kJsonMissionObjectKey] = missionJson;
planJson[kJsonGeoFenceObjectKey] = fenceJson;
planJson[kJsonRallyPointsObjectKey] = rallyJson;
...
return QJsonDocument(planJson);
}
JSON 文件头含 fileType: "Plan"、version: 1,便于版本兼容检查。
写文件(saveToFile):QFile 打开 → saveDoc.toJson() 写入磁盘 → 更新 _currentPlanFile → 离线模式下清除 dirty 位。
插件钩子: QGCCorePlugin::preSaveToJson / postSaveToJson / preSaveToMissionJson 允许定制版在 JSON 中插入私有字段,不影响标准字段解析。
5.2.5 导入逻辑
PlanMasterController::loadFromFile(filename) 按扩展名分支:
.plan/.mission:JSON 解析 → 分别调用_missionController.load()、围栏/集结点 load.waypoints/.txt:文本解析为航点列表- 加载前后调用
preLoadFromJson/postLoadFromJson插件钩子
MissionController::load() 从 JSON 数组重建 VisualMissionItem 列表:首项通常为 MissionSettingsItem,后续按 type 字段实例化 SimpleMissionItem 或各 ComplexMissionItem 子类。
KML 导入(区域):
QGCMapPolygon::loadKMLOrSHPFile() + ShapeFileHelper 解析多边形,用于 Survey 区域导入,而非逐点航点 KML。
KML 导出(航点路径):
MissionController::convertToKMLDocument() → 先 _convertToMissionItems() 展开为 MAVLink 列表 → Kml::points(coords) → kml.save(document)。
Plan 视图 UI 入口:
loadFromSelectedFile():文件对话框,过滤.plan/.missionsaveToSelectedFile():保存完整计划saveKmlToSelectedFile():仅导出 KML 航点
5.3 任务下发、断点续飞、任务终止
5.3.1 MAVLink 任务协议(PlanManager)
PlanManager 是 MAVLink Mission Protocol v2 的 状态机实现,被 MissionManager(任务)、GeoFenceManager、RallyPointManager 继承,通过 _planType 区分 MAV_MISSION_TYPE_MISSION 等。
事务类型:
enum TransactionType {
TransactionNone,
TransactionRead, // 从飞控下载
TransactionWrite, // 上传到飞控
TransactionRemoveAll // 清空飞控任务
};
上传状态机流程:
writeMissionItems(items)
→ 复制到 _writeMissionItems,调整 seq(Home 处理)
→ _writeMissionItemsWorker()
→ _setTransactionInProgress(TransactionWrite)
→ _connectToMavlink() // 独占链路,过滤 MISSION 消息
→ _writeMissionCount() // 发送 MISSION_COUNT
← AckMissionRequest // 飞控请求第 i 项
→ _handleMissionRequest() // 发送 MISSION_ITEM_INT
← AckMissionItem // 飞控确认
... 循环直到全部写入 ...
→ _finishTransaction(success)
→ emit sendComplete(error)
→ emit progressPct(100)
核心代码:
void PlanManager::_writeMissionItemsWorker(void)
{
...
_setTransactionInProgress(TransactionWrite);
_connectToMavlink();
_writeMissionCount();
}
void MissionController::sendItemsToVehicle(Vehicle* vehicle, QmlObjectListModel* visualMissionItems)
{
if (vehicle) {
QList<MissionItem*> rgMissionItems;
_convertToMissionItems(visualMissionItems, rgMissionItems, vehicle);
vehicle->missionManager()->writeMissionItems(rgMissionItems);
}
}
Home 点处理: PX4 通常 seq=0 为 Home;APM 可能跳过 Home。FirmwarePlugin::sendHomePositionToVehicle() 决定是否上传 Home 项及序列号偏移。
超时重试: _startAckTimeout() + _retryCount,超时后重发当前步骤,防止链路丢包导致上传卡死。
进度反馈: progressPct 信号 → Plan 视图进度条;MissionController 转发为 Q_PROPERTY progressPct。
5.3.2 Plan 视图上传流程
PlanView.qml 中 upload() 函数:
checkReadyForSaveUpload(false)检查数据完整性- 若飞机已解锁且在 Mission 模式,弹出确认对话框(防止飞行中覆盖任务)
- 调用
_planMasterController.sendToVehicle()
PlanMasterController::sendToVehicle() 顺序下发三个计划元素:
MissionController.sendToVehicle()
→ (完成后) GeoFenceController.sendToVehicle()
→ (完成后) RallyPointController.sendToVehicle()
这种 链式回调(_sendMissionComplete → _sendGeoFenceComplete → _sendRallyPointsComplete)保证飞控按依赖顺序接收。
5.3.3 从飞控下载任务
PlanMasterController::loadFromVehicle() → MissionController::loadFromVehicle() → MissionManager::loadFromVehicle() → PlanManager 读事务:
MISSION_REQUEST_LIST → MISSION_COUNT ←
MISSION_REQUEST_INT(0..N-1) → MISSION_ITEM_INT →
newMissionItemsAvailable 信号 → MissionController 重建 visualItems
Fly 视图连接 _missionManager::newMissionItemsAvailable 自动刷新航线显示;Plan 视图仅在 _itemsRequested 或空计划时接受覆盖。
5.3.4 断点续飞(Resume Mission)
场景: 任务执行中因低电量/手动介入返航,落地更换电池后从断点继续。
续飞索引计算(MissionController::resumeMissionIndex):
int MissionController::resumeMissionIndex(void) const
{
int resumeIndex = 0;
if (_flyView) {
resumeIndex = _missionManager->lastCurrentIndex() + (... Home 偏移 ...);
if (resumeIndex > 1 && resumeIndex != 最后一项序列号) {
resumeIndex--; // 回到「正在前往」的前一项
} else {
resumeIndex = 0;
}
}
return resumeIndex;
}
依赖 MissionManager::lastCurrentIndex()——飞控上报的「上一个已完成航点」索引。
续飞任务生成(MissionManager::generateResumeMission):
- 拒绝含
MAV_CMD_DO_JUMP的任务(无法安全截断) - 将
resumeIndex对齐到「带坐标的飞行命令」 - 保留
resumeIndex之前的配置类 DO 命令(ROI、相机、速度等) - 保留
resumeIndex及之后所有航点 - 去重前缀 DO 命令(只保留最后一个 ROI 等)
- 重排序列号,设置
isCurrentItem _resumeMission = true→_writeMissionItemsWorker()上传
void MissionManager::generateResumeMission(int resumeIndex)
{
...
for (int i=0; i<_missionItems.count(); i++) {
if (item->command() == MAV_CMD_DO_JUMP) {
qgcApp()->showMessage(tr("Unable to generate resume mission due to MAV_CMD_DO_JUMP command."));
return;
}
}
resumeIndex = qMax(0, qMin(resumeIndex, _missionItems.count() - 1));
上传完成后:
if (_resumeMission) {
emit resumeMissionReady(); // 或 resumeMissionUploadFail()
}
UI 入口: FlightDisplay/GuidedActionsController.qml——落地后提示「Resume Mission」,调用 MissionController::resumeMission(resumeIndex)。
5.3.5 任务终止与清除
QGC 没有 名为 abortMission() 的统一 API。任务「终止」分几个层次:
| 操作 | 实现 | 效果 |
|---|---|---|
| 清空飞控任务 | removeAllFromVehicle() → MISSION_CLEAR_ALL | 飞控任务列表为空 |
| 清空本地计划 | removeAll() | 仅清编辑器 |
| 飞行中暂停 | 切换至 Pause/Hold 模式 | Vehicle + GuidedActionsController |
| 返航 | RTL 模式 | 固件插件 FirmwarePlugin |
| 中止降落 | Vehicle::abortLanding() | 仅固定翼降落中止 |
void MissionController::removeAllFromVehicle(void)
{
...
_missionManager->removeAll();
}
Plan 视图工具栏也有「Remove all from vehicle」按钮(PlanView.qml 约 988 行)。
启动任务: Vehicle::startMission() → FirmwarePlugin::startMission(),PX4 发 MAV_CMD_MISSION_START,APM 切 AUTO 模式。
5.4 地理坐标换算算法
QGC 地理计算采用 双层体系:日常航点间距用 Qt 内置球面公式;测绘/条带等平面几何用 NED 局部坐标;地图投影用 GeographicLib。
| 体系 | 实现 | 典型用途 |
|---|---|---|
| WGS84 经纬高 | QGeoCoordinate | 航点存储、MAVLink 下发、地图显示 |
| 局部切平面 NED | convertGeoToNed / convertNedToGeo | 多边形编辑、Survey 条带生成、面积计算 |
| UTM / MGRS | GeographicLib | 坐标格式显示与输入 |
| 球面测距/方位 | QGeoCoordinate::distanceTo/azimuthTo | 航段距离、任务统计、分割航点 |
核心算法:QGCGeo(NED / UTM / MGRS)
文件: src/Geo/QGCGeo.cc、src/Geo/QGCGeo.h
参考: PX4 geo.c、局部切平面文献(头文件注释链接)
2.1 经纬度 → NED(convertGeoToNed)
将以 origin 为原点的 WGS84 坐标投影到局部北-东-地(NED)平面,单位:米。
常量:
[
R = 6371000 \text{ m}, \quad \lambda = \frac{\pi}{180}
]
步骤:
- 转弧度:(\varphi, \lambda)(目标点),(\varphi_0, \lambda_0)(原点)
- 角距:
[
c = \arccos\bigl(\sin\varphi_0\sin\varphi + \cos\varphi_0\cos\varphi\cos(\lambda-\lambda_0)\bigr)
] - 比例因子((c \to 0) 时取 (k=1) 防 NaN):
[
k = \begin{cases} c/\sin c & |c| > \varepsilon \ 1 & \text{否则} \end{cases}
] - 输出:
[
x_\text{N} = k \cdot R \cdot (\cos\varphi_0\sin\varphi - \sin\varphi_0\cos\varphi\cos(\lambda-\lambda_0))
]
[
y_\text{E} = k \cdot R \cdot \cos\varphi \cdot \sin(\lambda-\lambda_0)
]
[
z_\text{D} = -(h - h_0)
]
void convertGeoToNed(QGeoCoordinate coord, QGeoCoordinate origin, double* x, double* y, double* z)
{
...
double c = acos(ref_sin_lat * sin_lat + ref_cos_lat * cos_lat * cos_d_lon);
double k = (fabs(c) < epsilon) ? 1.0 : (c / sin(c));
*x = k * (ref_cos_lat * sin_lat - ref_sin_lat * cos_lat * cos_d_lon) * CONSTANTS_RADIUS_OF_EARTH;
*y = k * cos_lat * sin(lon_rad - ref_lon_rad) * CONSTANTS_RADIUS_OF_EARTH;
*z = -(coord.altitude() - origin.altitude());
}
使用位置:
QGCMapPolygon::nedPolygon()— 多边形转平面坐标SurveyComplexItem::_rebuildTransectsPhase1WorkerSinglePolygon()— Survey 区域转 NED 后生成平行扫描线QGCMapPolygon::_pointFFromCoord()— 点在多边形内判断(containsCoordinate)
2.2 NED → 经纬度(convertNedToGeo)
逆变换,将局部 ((x,y,z)) 转回 WGS84:
[
c = \sqrt{(x/R)^2 + (y/R)^2}, \quad x_r = x/R,\ y_r = y/R
]
[
\varphi = \arcsin\Bigl(\cos c \cdot \sin\varphi_0 + \frac{x_r \sin c \cdot \cos\varphi_0}{c}\Bigr)
]
[
\lambda = \lambda_0 + \operatorname{atan2}\Bigl(y_r\sin c,\ c\cos\varphi_0\cos c - x_r\sin\varphi_0\sin c\Bigr)
]
[
h = -z + h_0
]
使用位置:
- Survey 条带线段端点从 NED 转回 Geo(
convertNedToGeo(line.p1().y(), line.p1().x(), ...)) QGCMapPolygon::offset()— 多边形外扩/内缩后顶点回写QGCMapPolygon::_coordFromPointF()— 质心计算
2.3 UTM / MGRS(GeographicLib)
QGC 不自行推导 UTM 级数展开,直接调用 GeographicLib:
GeographicLib::UTMUPS::Forward(coord.latitude(), coord.longitude(), zone, northp, easting, northing);
MGRS 在 UTM 基础上再编码为军用网格字符串。
用途: 坐标显示格式转换,不参与 Survey/航点核心计算。
Qt 球面公式:QGeoCoordinate
来源: Qt Location 模块(Haversine 大圆公式)
| 方法 | 公式含义 | 使用位置 |
|---|---|---|
distanceTo(coord) | 两点大圆距离 (d)(米) | 任务总距、航段长度、包围盒宽高 |
azimuthTo(coord) | 起始方位角 (\theta)(度,北=0) | 航向箭头、分割航点、条带内插 |
atDistanceAndAzimuth(d, θ) | 从当前点沿方位走 (d) 米得新点 | 多边形平移、条带中间点、圆形边界 |
MissionController 航段统计:
*distance = prevCoord.distanceTo(currentCoord);
*azimuth = prevCoord.azimuthTo(currentCoord);
与 NED 的分工:
- 小范围(Survey 区域、围栏):NED 平面几何更稳定
- 航点链统计、UI 距离显示:直接用
QGeoCoordinate球面公式
QGCGeoBoundingCube — 任务 3D 包围盒
文件: src/QmlControls/QGCGeoBoundingCube.h/.cc
4.1 数据结构
用两个对角点描述轴对齐包围盒(不考虑地球曲率,适用于局部小范围):
pointNW:西北上角(max lat, min lon, max alt)pointSE:东南下角(min lat, max lon, min alt)
初始化 reset() 设为无效哨兵值(lat=90/-90 等)。
4.2 计算公式
| 方法 | 公式 | 说明 |
|---|---|---|
center() | (\text{lat} = \frac{(\text{lat}\text{NW}+90)+(\text{lat}\text{SE}+90)}{2}-90) | 经度同理;高度取算术平均 |
width() | pointNW.distanceTo(NE角) | NE = (lat_NW, lon_SE) |
height() | pointNW.distanceTo(SW角) | SW = (lat_SE, lon_NW) |
area() | (\frac{h}{1000} \times \frac{w}{1000}) km² | 平面近似 |
radius() | pointNW.distanceTo(pointSE) / 2 | 对角线半长 |
polygon2D(clipTo) | 若 area > clipTo,以 center 为中心、side=(\sqrt{clipTo}) 的正方形 | 空域管理裁剪 |
5.5 关键源码文件索引
| 模块 | 路径 |
|---|---|
| 任务控制器 | src/MissionManager/MissionController.h/.cc |
| 计划总控 | src/MissionManager/PlanMasterController.h/.cc |
| MAVLink 协议 | src/MissionManager/PlanManager.h/.cc |
| 任务专用 Manager | src/MissionManager/MissionManager.h/.cc |
| 简单航点 | src/MissionManager/SimpleMissionItem.h/.cc |
| 复杂任务基类 | src/MissionManager/ComplexMissionItem.h/.cc |
| 条带扫描 | src/MissionManager/TransectStyleComplexItem.h/.cc |
| MAV 命令定义 | src/MissionManager/MavCmdInfoCommon.json |
| 命令树 | src/MissionManager/MissionCommandTree.h/.cc |
| 地理换算 | src/Geo/QGCGeo.h/.cc |
| Plan 视图 | src/PlanView/PlanView.qml |
| 续飞 UI | src/FlightDisplay/GuidedActionsController.qml |
| KML 工具 | src/MissionManager/KML.h |
5.7 本章小结
QGroundControl 4.0 的任务与航线系统以 VisualMissionItem(用户模型)→ MissionItem(协议模型)→ PlanManager(传输协议) 三层解耦为核心。航点、Loiter 绕圈、悬停拍照分别通过 SimpleMissionItem 的 MAV_CMD、ComplexMissionItem 的条带算法、以及 MissionFlightStatus 统计实现。PlanMasterController 统一管理 .plan/.mission/KML/文本格式的持久化。上传走 MAVLink Mission Protocol v2 状态机,续飞通过 generateResumeMission 截断并重排任务,终止则通过 CLEAR_ALL 或飞行模式切换完成。地理计算在 Qt 球面 API 与 NED 局部平面之间按场景选用,Survey 等复杂任务依赖 convertGeoToNed 平面化后做几何运算。

8402

被折叠的 条评论
为什么被折叠?



