QGC无人机任务与航线开发

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()                      │
└─────────────────────────────────────────────────────────────┘

涉及的主要设计模式:

模式应用场景
MVCQML View ↔ C++ Controller ↔ MissionItem Model
组合模式VisualMissionItem::appendMissionItems() 展开为多条 MAVLink 指令
模板方法TransectStyleComplexItem::_rebuildTransects() 由子类实现 Phase1
策略模式FirmwarePlugin 按 PX4/APM 差异处理 Home、续飞、起飞
观察者(信号槽)dirtyChangedsendCompleteprogressPctresumeMissionReady
Fact 系统任务参数、高度、速度等统一绑定 UI
工厂/注册MissionCommandTree 按固件/机型过滤可用 MAV 命令

涉及语法与技术:

  • C++11:QObject 继承、Q_PROPERTYQ_ENUMQ_INVOKABLE
  • Qt 元对象:signals/slotsconnect()qobject_cast
  • QML:LoaderRepeaterMouseArea、属性绑定
  • 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 视图共享的可视化基类,暴露 coordinateexitCoordinatecommandName 等 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 视图):

  1. 用户在 PlanView.qml 地图 MouseArea 点击 → 调用 MissionController.insertSimpleMissionItem(coordinate, command)
  2. MissionController::_insertSimpleMissionItemWorker() 创建 SimpleMissionItem,设置序列号、坐标、命令
  3. 若新点需要高度,调用 _findPreviousAltitude() 继承前一点高度模式
  4. _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_UNLIM17无限期盘旋
MAV_CMD_NAV_LOITER_TURNS18指定圈数
MAV_CMD_NAV_LOITER_TIME19指定时间
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」命令类型后,SimpleMissionItemcommand 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/hoverTimecruiseDistance/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 典型流程:

  1. 用户在地图绘制 QGCMapPolygon
  2. 设置相机参数(CameraCalc)、间距、角度
  3. _rebuildTransectsPhase1() 在 NED 平面生成平行线
  4. appendMissionItems() 输出大量 MAV_CMD_NAV_WAYPOINT + 相机触发指令

Plan 视图对应编辑器:SurveyItemEditor.qmlCorridorScanEditor.qmlStructureScanEditor.qml;地图可视化:SurveyMapVisual.qmlTransectStyleMapVisuals.qml

5.1.6 MissionCommandTree:命令注册与过滤

MissionCommandTreeQGCToolbox 工具)从 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 视图编辑链路:

  1. 地图打点PlanView.qmlFlightMap + MouseAreainsertSimpleMissionItem / insertTakeoffItem / insertLandItem
  2. 属性编辑:选中航点 → MissionItemEditor.qml / SimpleItemEditor.qml 绑定 VisualMissionItem 的 Fact
  3. 复杂任务创建:工具栏选择 Survey 等 → insertComplexMissionItem 或从 KML/SHP 导入区域
  4. 拖拽调整*MapVisual.qmlDragArea 修改坐标 → setCoordinate_recalcAll
  5. 分割/删除splitSegmentremoveVisualItemmoveVisualItem

MissionControllerQmlObjectListModel* _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

扩展名类型内容
.planJSON完整计划(mission + geoFence + rallyPoints)
.missionJSON仅任务部分
.waypoints / .txt文本QGC/APM 兼容航点列表
.kmlKML航点路径导出(仅坐标序列)

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/.mission
  • saveToSelectedFile():保存完整计划
  • saveKmlToSelectedFile():仅导出 KML 航点

5.3 任务下发、断点续飞、任务终止

5.3.1 MAVLink 任务协议(PlanManager)

PlanManager 是 MAVLink Mission Protocol v2 的 状态机实现,被 MissionManager(任务)、GeoFenceManagerRallyPointManager 继承,通过 _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.qmlupload() 函数:

  1. checkReadyForSaveUpload(false) 检查数据完整性
  2. 若飞机已解锁且在 Mission 模式,弹出确认对话框(防止飞行中覆盖任务)
  3. 调用 _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):

  1. 拒绝含 MAV_CMD_DO_JUMP 的任务(无法安全截断)
  2. resumeIndex 对齐到「带坐标的飞行命令」
  3. 保留 resumeIndex 之前的配置类 DO 命令(ROI、相机、速度等)
  4. 保留 resumeIndex 及之后所有航点
  5. 去重前缀 DO 命令(只保留最后一个 ROI 等)
  6. 重排序列号,设置 isCurrentItem
  7. _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 下发、地图显示
局部切平面 NEDconvertGeoToNed / convertNedToGeo多边形编辑、Survey 条带生成、面积计算
UTM / MGRSGeographicLib坐标格式显示与输入
球面测距/方位QGeoCoordinate::distanceTo/azimuthTo航段距离、任务统计、分割航点

核心算法:QGCGeo(NED / UTM / MGRS)

文件: src/Geo/QGCGeo.ccsrc/Geo/QGCGeo.h
参考: PX4 geo.c、局部切平面文献(头文件注释链接)

2.1 经纬度 → NED(convertGeoToNed

将以 origin 为原点的 WGS84 坐标投影到局部北-东-地(NED)平面,单位:米。

常量:
[
R = 6371000 \text{ m}, \quad \lambda = \frac{\pi}{180}
]

步骤:

  1. 转弧度:(\varphi, \lambda)(目标点),(\varphi_0, \lambda_0)(原点)
  2. 角距:
    [
    c = \arccos\bigl(\sin\varphi_0\sin\varphi + \cos\varphi_0\cos\varphi\cos(\lambda-\lambda_0)\bigr)
    ]
  3. 比例因子((c \to 0) 时取 (k=1) 防 NaN):
    [
    k = \begin{cases} c/\sin c & |c| > \varepsilon \ 1 & \text{否则} \end{cases}
    ]
  4. 输出:
    [
    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
任务专用 Managersrc/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
续飞 UIsrc/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 平面化后做几何运算。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值