- 无人机
- 智能硬件
【免费下载链接】qgroundcontrol
Cross-platform ground control station for drones (Android, iOS, Mac OS, Linux, Windows)
导读
GeoMap 是 QGroundControl(QGC)项目中一个基于 Qt Quick 3D 构建的自研 2D/3D 地图引擎,目前作为 QtLocationMap的实验性替代方案(experimental preview)存在。它用真实高程数据塑造地面网格,并将标准地图瓦片影像铺贴其上,通过手势驱动的相机在经典俯视 2D 地图与倾斜 3D 视角之间平滑过渡。本文以 src/GeoMap/README.md 为主体,结合 src/GeoMap 下的源码实现与 test/GeoMap 测试用例,系统讲解其 patch 四叉树 LOD 机制、高程/影像双数据管线、场景与相机模型、覆盖物体系、坐标系与线程模型,帮助读者理解这套引擎的设计思路与底层原理。
引擎定位:为什么需要一套自研地图引擎
在 QGC 中,传统地图功能由 QtLocation 插件承担:地图瓦片获取逻辑深埋在 QtLocation 插件内部,只能喂给 QtLocation 的Map控件,QGC 无法直接拿到原始QImage瓦片。GeoMap 的核心动机正是打破这一限制:
- 它需要把瓦片图像作为Quick 3D 纹理上传到 GPU,因此必须绕开 QtLocation 的渲染管线,直接获得瓦片原始像素;
- 它需要把真实高程叠加到地图影像上,形成可倾斜观察的 3D 地形;
- 它需要提供 Google Earth 式的手势相机(平移、环绕、缩放、扭转、第一人称环视)。
从 src/GeoMap/CMakeLists.txt 可以看出,GeoMap 被组织为一个独立的 Qt 模块(GeoMapModule,URI 为QGroundControl.GeoMap),依赖QtQuick3D、QtPositioning等,其 QML 面(GeoMap.qml及各类 MapItem)与 C++ 面(相机、场景、网格、数据源)分层清晰。引擎当前被 FlyView 使用,入口为 src/FlyView/FlyViewGeoMap.qml(配合 src/FlyView/FlyViewGeoMapAdapter.qml),作为传统FlyViewMap的对应物存在。
总体架构:Patch 四叉树与"形状 + 外观"双输入
GeoMap 的可见地面由一个个正方形patch(片)组成。patch 的选择遵循 slippy-map 瓦片四叉树:靠近相机的 patch 精细、远处的 patch 粗犷,每个 patch 需要两样东西:
- 形状(高程):一个高度网格,将 patch 网格在垂直方向位移;
- 外观(影像):一张标准地图瓦片图像,铺贴在网格表面。
两者都走cache-first(缓存优先)策略:先查 QGC 共享的瓦片缓存数据库,未命中才走网络,拉取回来的瓦片再写回缓存。因此 GeoMap 下载的瓦片与普通地图瓦片共享同一套缓存,双向复用(详见后文TerrariumTileFetcher与TileImageSource)。
相机移动时,patch 集合被持续精炼:在相机注视的方向增加细节,丢弃屏幕外的 patch,整个过程增量进行以保证帧率稳定。2D↔3D 切换本质上只是一段动画:倾斜相机的同时,将地形位移量在 0(平坦 2D)与 1(完整 3D)之间缩放,不重建任何几何体。这一点在 src/GeoMap/GeoMap.qml 中体现为terrainAnimation与tiltAnimation两个并行的NumberAnimation,并且单个Node上以scale: Qt.vector3d(1, 1, Math.max(0.0001, geoScene.terrainScale * geoScene.verticalScale))一处绑定替代 N 个 delegate 各自的绑定(注释明确指出:delegate 创建成本主导 patch 变更成本,且过渡期间每个 delegate 重复求值会浪费性能)。
关键实现类一览
| 类 | 职责 | 源码位置 |
|---|---|---|
SurfaceModel | 维护当前相机视角下的 patch 集合(四叉树精炼、裁剪、增删预算) | src/GeoMap/SurfaceModel.h |
SurfacePatchModel | QML 桥接层与管线所有者,以QAbstractListModel暴露 patch 集合 | src/GeoMap/SurfacePatchModel.h |
HeightField | 连续高程场:任意位置都能给出最佳估计高度 | src/GeoMap/HeightField.h |
ElevationTilePyramid | 已解码高程瓦片的内存 LRU 工作集 | src/GeoMap/ElevationTilePyramid.h |
TerrariumTileFetcher | AWS Open Data Terrain Tiles 高程抓取器 | src/GeoMap/TerrariumTileFetcher.h |
TileImageSource | 地图瓦片影像抓取器(缓存优先) | src/GeoMap/TileImageSource.h |
GeoScene | 共享渲染空间:场景原点、垂直缩放、地形缩放 | src/GeoMap/GeoScene.h |
GeoMapCamera | 相机姿态模型(center/heading/tilt/distance)与手势数学 | src/GeoMap/GeoMapCamera.h |
GeoMapItem | 覆盖物基类(QtLocationMapQuickItem的对应物) | src/GeoMap/GeoMapItem.h |
PatchGeometry | 单个 patch 的网格几何体(含裙边与边拼接) | src/GeoMap/PatchGeometry.h |
PatchTextureData | patch 瓦片图像的 Quick 3D 纹理 | src/GeoMap/PatchTextureData.h |
高程数据管线:terrarium 瓦片与连续高度场
数据源:AWS Open Data Terrain Tiles
高程数据来自AWS Open Data Terrain Tiles数据集——全球高程以terrarium 编码的 PNG形式提供,寻址方式与地图瓦片完全一致(z/x/y)。该数据集最高提供到zoom 15;超过 z15 的请求由 z15 祖先瓦片满足,因此影像可以继续细化到更高 zoom,而高程停留在 z15 上限。对应源码常量见 src/GeoMap/TerrariumTileFetcher.h:
static constexpr int kMaxTileZoom = 15; // terrarium 数据集最高服务层级 static constexpr int kAnchorZoom = 10; // 细瓦片的粗祖先锚定层级抓取与"锚定"机制
TerrariumTileFetcher实现了抽象接口HeightSource(src/GeoMap/HeightSource.h)。它的工作方式:
- 缓存优先:先查 QGC 共享瓦片数据库,未命中则走网络,拉取结果写回缓存;
- 锚定(anchor):每个比 z10 更细的瓦片,都会同时抓取自身及其邻居的 z10 祖先,并且直到这些祖先全部到位才释放细瓦片——否则细数据旁边可能紧邻完全没有数据的区域,该区域会按 0 米渲染,形成悬崖;
- 失败不阻塞:某个锚定抓取失败时,不再继续等待——"有悬崖风险的细数据"优于"没有细数据"。
抓取到的瓦片经解码后落入HeightField,并通过regionChanged信号通知消费者。失败的抓取什么都不插入,字段继续提供当前最佳估计。
HeightField:永无空洞的连续高程场
src/GeoMap/HeightField.h 是这套设计的核心抽象:它对外呈现一个连续的高程场,能够回答"这里地面多高"这一问题,且答案永远存在:
- 有瓦片处:真实数据;
- 仅有更粗祖先瓦片处:从祖先插值得到的估计值(双线性采样,采样中心采用像素中心约定并在瓦片边缘钳制,与 terrarium 瓦片布局一致);
- 完全未知处:0 米。
地形永远不会出现空洞,只有暂时更粗糙的地形。这意味着 patch 总能立即用当前最佳估计完成网格化;当更好的数据到达时,受影响的 patch 重新网格化。这在 test/GeoMap/HeightFieldTest.cc 等测试中被系统性验证。
HeightField内部把瓦片存放在ElevationTilePyramid(src/GeoMap/ElevationTilePyramid.h)中——这是一个内存 LRU 工作集:
- 容量上限
kMaxTiles = 128;每个 256×256 的 float 瓦片约 256 KB,因此内存上界约 32 MB; - 查询从自身或最近存储的祖先(取子窗口采样)解析,绝不返回后代,保证任何有祖先数据的地方覆盖都是连续的;
- 支持
setPinnedKeys钉住支撑屏幕 patch(及其邻居)的瓦片,防止其被逐出;当所有常驻瓦片都被钉住时,插入会突破软上限而不是破坏已渲染网格。
边界缝合:按位置解析的边顶点
patch 边缘顶点按世界位置解析(HeightField::samplePatch的注释明确说明:边界顶点按规范世界位置解析,而非 patch 自身的数据视图),因此相邻 patch 采样得到逐位相同的边高度,永不开裂。当某条边采用了与 patch 自身不同的数据(例如邻居的细瓦片)时,差异被混合到 patch 内部而非在最后一格突兀落下——在双方数据一致之前,它读起来像坡道而不是悬崖。此外gridSize必须是 2 的幂,顶点 UV 才是精确的 dyadic double,这是上述逐位一致保证的前提。
合成高程源:Flat 与 Debug
HeightSource接口还提供了两个合成实现(均在 src/GeoMap/HeightSource.h 中):
FlatHeightSource:处处 z=0,用于关闭地形时(它故意不插入全零瓦片,以免无意义地翻腾金字塔工作集);DebugHeightSource:解析式生成的确定性小山(峰值 200 米、间距 4000 米,见kAmplitude/kWavelength常量),用于在无网络、无地形数据的条件下离线验证网格位移与 LOD。
两者经由ProceduralHeightSource基类走事件循环异步投递,模拟真实瓦片投递流程。
影像数据管线:从瓦片到 Quick 3D 纹理
patch 影像使用 QGC 各处通用的 provider 瓦片(Google Satellite、OpenStreetMap 等)。由于 QGC 常规瓦片抓取封装在只能喂给 QtLocationMap的插件内,GeoMap 需要原始QImage上传为 Quick 3D 纹理,因此有了TileImageSource(src/GeoMap/TileImageSource.h):
- 复用共享组件(缓存数据库、provider/URL 注册表),同样采用缓存优先策略;
mapType是来自 provider 注册表(UrlFactory::getProviderTypes())的类型字符串,如"Google Satellite";未知类型会让所有请求(异步)失败,不触碰缓存与网络;- 结果总是异步投递;取消的请求不发信号;不内置重试——失败即保持失败,由消费者按自己的节奏(patch 逐出与重新加入)重新请求。
加载期占位合成:地面永不闪空
当某个 patch 自己的影像还在加载时,SurfacePatchModel会合成占位图像:
- 该 patch 的四个子瓦片若可用则合成(更清晰);
- 否则从最近的祖先瓦片裁剪一块;
- 已被移除 patch 的图像被退役(retired)而非丢弃,作为 LOD 变化期间的占位来源保留。
相关常量同样在 src/GeoMap/SurfacePatchModel.h:kMaxRetiredImages = 128(patch 移除后保留的瓦片数,作为占位源)、kMaxAncestorFallbackLevels = 8(四叉树向上查找占位的最大层级)、kImageRetryMs = 3000(失败瓦片影像重请求的节流间隔)。
表面模型:数据如何变成网格
SurfaceModel:决定"有哪些 patch"
SurfaceModel(src/GeoMap/SurfaceModel.h)负责维护当前视角下的 patch 集合。每次相机变化时:
- 估算可见地面区域:通过相机采样屏幕点(
kVisibleSampleGrid = 5的 N×N 网格),地平线未命中处以距离缩放的范围封顶(kMaxRangeMultiplier = 40.0,即最多 40 倍相机距离),因此高倾斜下的远地平线覆盖是近似的(那里的粗 patch 可以接受); - 四叉树细分:任何投影尺寸超过~384 px(
kRefinePixelThreshold)的 patch 都会被细分,子 patch 每边kGridSize = 16个网格单元; - 差异对比:将期望集合与当前集合做 diff。
变更是有预算的:因为创建渲染 delegate(几何体 + 纹理,桌面端约 0.75 ms)是同步且昂贵的,每次更新最多新增kMaxPatchAddsPerUpdate = 2个 patch,最多移除kMaxPatchRemovalsPerUpdate = 4个(移除预算大于新增预算,使持续变更下排水快于填充),剩余部分由后续自我调度的更新轮次继续。因此快速缩放时细节是在若干帧内流式出现的,而不是卡顿一下。全集合上限kMaxPatches = 256,最坏情况下驻留集合会因上一代 LOD 的退役 patch 覆盖待替换 patch 而瞬时超出约 2 倍。
patch 是连续HeightField的视图:新 patch 立即用字段的最佳估计网格化(绝不是旁边有真实邻居却用平坦占位的状态),瓦片覆盖从高度源请求,数据到达后regionChanged只重新网格化触及变更区域的 patch。跨 LOD 变更时,一个 patch 只有在其替换者驻留之后才允许被移除,因此 LOD 抖动与平移永远不会在地表撕开空洞。测试见 test/GeoMap/SurfaceModelTest.cc。
SurfacePatchModel:QML 桥接与管线所有者
SurfacePatchModel(src/GeoMap/SurfacePatchModel.h)是连接 C++ 与 QML 的QAbstractListModel(每行一个 patch,增量更新,平移/缩放不做模型重置),同时是管线所有者:
- 依据属性(
terrain、mapType、debugHills)构造高度源、HeightField、SurfaceModel与TileImageSource; - 向 QML 暴露角色:patch 中心(场景米)、跨度、四叉树 zoom、高度网格、瓦片图像、边 LOD 差值、瓦片 x/y 等;
- 提供
terrainHeightAt(渲染表面高度)与terrainDataHeightAt(字段估计高度)两个查询入口——前者供相机枢轴与海拔基准使用,避免耦合 patch 抖动; - 提供
surfaceCoordinateAtScreenPoint:沿相机拾取射线在z = 地形高度(x, y) * zScale的曲面上做步进求交,拾取落在可见前表面,山脊会遮挡其后的地面; - 提供性能统计(
statsText,每秒采样)、性能抓取(startCapture/stopCapture,输出 CSV)与诊断(analyzeSurface)。
在 src/GeoMap/GeoMap.qml 中,Repeater3D以 patchModel 为模型,每个 delegate 是一个 Quick 3DModel,组合:
PatchGeometry(src/GeoMap/PatchGeometry.h):网格网格((gridSize+1)^2顶点,原点在 patch 中心,x 东 y 北 z 上),高度网格位移,四边悬挂裙边(基础深度为跨度的 5%,按2^maxEdgeDelta缩放)隐藏不同 LOD 相邻 patch 间的裂缝;非重合边顶点被折叠到重合顶点之间的线段上,杜绝 T 型接缝。已知局限:法线基于本 patch 高度网格的中心差分(边缘单侧),光照材质在 patch 边界会显示着色接缝,此问题留待光照地形相关里程碑解决;PatchTextureData(src/GeoMap/PatchTextureData.h):将瓦片QImage提供给 Quick 3DTexture;空图像产生空纹理,消费者依据图像有效性切换材质而不是渲染它。
纹理采用ClampToEdge环绕模式——默认的 Repeat 会在 UV 0/1 处渗入对边纹素,在每个 patch 边界画出 1 像素接缝。材质为无光照(NoLighting)且关闭背面剔除:瓦片自带明暗信息,光照反而会暴露 patch 法线接缝。影像关闭时(mapType为空),delegate 以 LOD 棋盘色(_lodColor)渲染,用于调试 LOD 分布;CheckerboardTextureData(src/GeoMap/CheckerboardTextureData.h)则提供程序化棋盘纹理用于离线验证 UV 映射。
SurfaceAnalysis:地表诊断
SurfaceAnalysis(src/GeoMap/SurfaceAnalysis.h)是对当前 patch 集合的诊断通道,检查:覆盖空洞、相机低于地表、patch 边界高度不连续(接缝)、patch 内部边阶跃、非有限高度数据(NaN/Inf 顶点),并对每种问题归类成因(SeamCause/HoleCause枚举,如LodTJunction、ElevatedTerrainCulled等)。通过SurfacePatchModel::analyzeSurface()触发,报告输出到调试日志。测试见 test/GeoMap/SurfaceAnalysisTest.cc。
此外,SurfaceModel内置"悬崖监视器":开启 GeoMap 调试 UI 时,每次出现超过半个网格单元跨度(kCliffLogSlope = 0.5,约 27°)的 patch 内边阶跃,都会以GeoMap.SurfaceModel.Cliffs分类记录。靠近 patch 角点的阶跃被忽略——角点解析到其他数据会留下一个小的预期缺口。
场景与相机:渲染空间与姿态模型
GeoScene:可重锚定的共享场景空间
GeoScene(src/GeoMap/GeoScene.h)定义所有 GeoMap 内容共享的渲染空间:
- 场景坐标是相对于可移动
sceneOrigin的 Web Mercator 米; - 当相机漂移超过50 km(
kReanchorDistance = 50000.0)时,原点重新锚定到相机中心,使场景坐标保持足够小,单精度 GPU 浮点在地球任意位置都保持精确; - 所有可渲染内容(地表 patch,以及后续的车辆、航迹、任务标记)都通过这一个原点定位,重锚定时整个场景一致移动;
- 拥有两个全局缩放因子:
verticalScale:Mercator 纬度校正(1/cos(lat))——场景 x/y 是 Mercator 米,只有纬度方向上被拉伸,渲染时必须把 z 按此因子缩放,否则几何体远离赤道会被垂直压扁;terrainScale:2D↔3D 过渡背后被动画驱动的 0–1 因子(启动模式为 2D,因此从 0 开始)。
GeoMapCamera:Google Earth 风格的手势相机
GeoMapCamera(src/GeoMap/GeoMapCamera.h)是相机姿态模型,姿态由 (center, heading, tilt, distance) 构成,其中 center 是地面上的地理坐标。关键常量:
static constexpr qreal kMinDistance = 10.0; // 最近距离(米) static constexpr qreal kMaxDistance = 40000000.0; // 最远距离(米) static constexpr qreal kMinTilt = 0.0; // 最小倾角(俯视) static constexpr qreal kMaxTilt = 85.0; // 最大倾角(最斜 3D 视角) static constexpr qreal kDefaultDistance = 1500.0; // 默认距离 static constexpr qreal kDefaultFieldOfView = 45.0; // 默认视场角 static constexpr qreal kDefault3DTilt = 55.0; // 3D 默认倾角相机位置约定(与 Viewer3D 环绕相机一致):
cameraPosition = center + distance * (sin(tilt)sin(heading), -sin(tilt)cos(heading), cos(tilt))手势语义镜像 Google Earth(在 src/GeoMap/GeoMap.qml 的处理器中有详尽注释):
| 手势 | 操作 | 语义 |
|---|---|---|
| 左键拖动 | 平移(beginPan/panTo) | 手势开始时光标下的地面点保持钉在光标下 |
| 右键/中键拖动,或 Shift+左键 | 环绕(beginOrbit/orbitTo) | 围绕按下时选中的地面点刚性旋转,满宽 = 360° 航向,满高 = 180° 倾角 |
| Ctrl+左键拖动 | 第一人称环视(beginLook/lookTo) | 相机位置固定,视线旋转(2D 模式锁定倾角,只绕相机垂直轴旋转) |
| 滚轮 | 缩放(zoom) | 光标下地面点保持不动 |
| 双指捏合 | 缩放/旋转(zoomBy/rotateBy/tiltBy) | 围绕质心缩放、扭转、竖直滑动倾角 |
两个值得注意的实现细节:
- 锚定必须贴到渲染地形表面:
beginPan/beginOrbit接受anchorZ参数——手势必须锚定到渲染的地形表面,而不是 z=0 地面平面,否则在起伏地形上地图会从光标下滑走(GeoMap.qml中的_surfaceZAt先在地面平面命中,再在 2 米高处精化一次); - 倾斜相机时视场角取大轴(Cesium 风格):
fieldOfView应用于较宽的视口轴,宽视口下垂直视场收窄而非水平视场爆成鱼眼畸变,所有投影数学均使用verticalFieldOfView。
投影数学(screenToGround、worldToScreen、groundPointCapped、pickRayAt)全部为双精度,支撑可见区域估计与覆盖物摆放。相机还提供distanceForZoomLevel/zoomLevelForDistance,使缩放级别语义与 QtLocation 一致。测试见 test/GeoMap/GeoMapCameraTest.cc(覆盖默认值、钳制、视场角计算、锚定不变量等)。
此外,相机有isPositioned()概念:构造默认姿态是"零岛"(0,0),消费方不得将其当作真实位置(例如在那里抓地形)。SurfaceModel在相机未定位前不构建任何 patch,避免注定失败的 (0,0) 地形抓取。src/GeoMap/GeoMap.qml 中启动姿态设为西雅图附近(47.6329078, -122.0876875),以便影像与高程直观可验证。
GeoMap.qml:顶层合成
src/GeoMap/GeoMap.qml 将上述全部元素合成为顶层地图控件:相机、场景、patch 模型、View3D、手势处理器、远景 LOD 环的雾霾(Fog朝向背景色#1a2028,隐藏 patch 范围边缘的远景 LOD 环)、2D↔3D 模式动画,以及 GCS 位置指示器。模式切换走统一路径(按钮、goTo2D/goTo3D、C++ 任意入口都一样):onModeChanged总是同时把 tilt 与 terrainScale 动画到对应目标,保证相机状态与地形位移永不脱钩;任何手势开始都会completeCameraAnimations()把进行中的模式/罗盘动画跳到终点,避免两者争夺同一姿态属性。
覆盖物:把东西放到地图上
GeoMapItem(src/GeoMap/GeoMapItem.h)是 QtLocationMapQuickItem的对应物:它把 QML 内容锚定到地理坐标,通过场景相机投影到View3D上方的 2D 图层。支持:
ClampToGround(默认)或Absolute海拔模式;- 可选
delegate3D——放在场景内部的 Quick 3D 节点,在模式过渡期间与 2D 内容交叉淡化(crossfade3D,默认开启;关闭后节点在两种模式下都保持不透明,适用于没有 2D 对应物的内容如航迹); - 当坐标在相机后方或超出地平线时,item 停在屏幕外且
projected为 false。
基于它构建的组件(均在 src/GeoMap 下):
GeoMapPin.qml——水滴形标记;GeoMapVehicleItem.qml——飞行器标记:2D 模式为平面图标,3D 模式为带完整姿态的PaperPlaneGeometry纸飞机;GeoMapFlightPath.qml——已飞航迹丝带(FlightPathGeometry,着色器广告牌条带、恒定屏幕宽度),随terrainScale动画回 0 而展平回经典 2D 航迹。
home 地形偏置(home terrain bias):车辆类 item 应用"高程数据集在家位置的高度 − 车辆上报的 home 海拔"之差,使降落的车辆精确坐在渲染地面上,即使 DEM 与 GPS 海拔不一致。该计算在GeoMap.qml的_updateHomeTerrainBias与 src/FlyView/FlyViewGeoMap.qml 的_activeVehicleHomeTerrainBias中实现。
坐标系:六层空间的转换链路
GeoMap 明确定义了六个坐标空间及其转换点(下表为 README 原文整理,转换函数均可在 src/GeoMap/TileMath.h 与 src/GeoMap/GeoScene.h 中找到):
| 空间 | 定义 | 转换发生处 |
|---|---|---|
| 大地(Geodetic) | QGeoCoordinate(WGS84 经纬高 AMSL) | TileMath::geoToWorld/worldToGeo |
| 世界(World) | Web Mercator(EPSG:3857)米;x 东、y 北、z 上 | 仅TileMath——唯一切换点 |
| 瓦片(Tile) | Slippy z/x/y(TileMath::TileKey),(0,0) 为西北角 | TileMath::tileForWorld/tileMinCorner |
| 场景(Scene) | 世界 −GeoScene::sceneOrigin;z 乘以verticalScale(terrainScale由渲染变换另行应用) | GeoScene::scenePositionFor |
| Patch 局部 | 原点在 patch 中心,±span/2 | PatchGeometry |
| 屏幕(Screen) | 经相机投影的像素 | GeoMapCamera::worldToScreen/screenToGround |
从 src/GeoMap/TileMath.h 可见其常量定义:WGS84 赤道半径kEarthRadius = 6378137.0,Mercator 纬度钳制 ±85.05112878°,zoom 范围 0–23,名义瓦片边长 256 px。世界坐标与投影数学为双精度;高程采样与渲染边界数据以 float 存储——这是远处大范围场景精度与近处 GPU 效率的折中。
线程模型:设计上的单线程
GeoMap刻意保持单线程(GUI 线程):
- 所有异步都是事件循环异步:高度源与影像源总是通过排队调用投递结果(即使答案立即可得),消费者看到一致的数据流——
HeightSource接口注释明确:请求结果总是异步(排队)投递,即使源可以立即回答; - 真实并发被委托给既有基础设施:瓦片缓存查询运行在瓦片引擎的工作线程,网络 I/O 在
QNetworkAccessManager,GPU 上传在 Qt Quick 3D 的渲染线程; - 因此
HeightField、ElevationTilePyramid均注明非线程安全,连 const 采样方法也会改写内部备忘/最近使用状态,并发读也会竞争。
如何查看与验证
GeoMap 是实验性预览功能,读者可在源码中直接研读其实现,并在启用了该预览的构建中体验:
- 阅读实现:核心 C++ 源码集中在 src/GeoMap(头文件含详尽设计注释,尤其 src/GeoMap/SurfaceModel.h、src/GeoMap/HeightField.h、src/GeoMap/GeoMapCamera.h),QML 合成在 src/GeoMap/GeoMap.qml,FlyView 集成在 src/FlyView/FlyViewGeoMap.qml;
- 运行单元测试:
test/GeoMap目录提供了覆盖各模块的单元测试(TileMathTest、HeightFieldTest、ElevationTilePyramidTest、GeoMapCameraTest、GeoSceneTest、SurfaceModelTest、SurfacePatchModelTest、SurfacePatchImageryTest、TerrariumTileFetcherTest、TileImageSourceTest、PatchGeometryTest、SurfaceAnalysisTest等),其中GeoMapCameraTest验证默认值、钳制、视场角与锚定不变量,SurfaceModelTest验证 patch 精炼与空洞防护; - 启用调试手段:
GeoMap提供renderStats(渲染统计浮层)、startPerfCapture/stopPerfCapture(每秒性能采样导出 CSV)、analyzeSurface(地表诊断)与 LOD 棋盘渲染(mapType为空时),debugHills属性可在无网络时用解析小山验证网格位移与 LOD。
结语
GeoMap 引擎展示了 QGC 在"传统 QtLocation 地图"之外的另一条技术路线:以 Qt Quick 3D 为渲染底座、以 slippy 瓦片四叉树为数据结构、以缓存优先与增量更新为性能纪律,构建出支持真实高程与 2D/3D 平滑过渡的自研地图。它的连续高度场设计(永无空洞)、按位置解析的边界缝合(永不开裂)、预算化的 patch 增删(快速缩放不卡顿)与可重锚定的场景原点(全球范围 float 精度)都是值得深入研读的实现范例。需要强调的是,README 与源码均将其定位为experimental preview——它是 QGC 地图能力的实验性探索,而非对既有地图方案的取代。
- 无人机
- 智能硬件
【免费下载链接】qgroundcontrol
Cross-platform ground control station for drones (Android, iOS, Mac OS, Linux, Windows)
相关推荐
QGroundControl GeoMap 引擎技术预览:3D 地形地图启用、视角操控与渲染原理
QGroundControl GeoMap 引擎技术预览:3D 地形地图启用、视角操控与渲染原理 导读 GeoMap 是 QGroundControl(QGC)
无人机智能硬件QGroundControl 5.1 之后的新特性解析:GeoMap 3D 地图引擎、ROI 目标高度与自定义设置页
QGroundControl 5.1 之后的新特性解析:GeoMap 3D 地图引擎、ROI 目标高度与自定义设置页 导读 本篇文章围绕 QGroundCont
无人机智能硬件QGroundControl What's New(V5.1 之后)实战指南:GeoMap 3D 地图引擎、ROI 高度与自定义设置页
QGroundControl What's New(V5.1 之后)实战指南:GeoMap 3D 地图引擎、ROI 高度与自定义设置页 本文面向从 QGroun
无人机智能硬件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考