资讯动态

Ubuntu下Qt嵌入百度地图HTML:QWebEngine与QWebChannel实战

发布时间:2026/10/19 7:52:12 来源:尧图企业网站定制
简介这份资源包面向需要在Ubuntu上使用Qt开发地图应用的开发者提供了一套以QWebEngine控件加载百度地图HTML的完整示例解决在Qt界面中显示地图并利用经纬度输入完成定位的实际需求。压缩包共9个文件包含HTML地图页面、C源文件.cpp/.h、界面设计.ui以及工程配置.pro大小仅9KB文件类型覆盖前后端交互的关键环节结构紧凑便于对照学习。目前已有1237人学习下载适合正在学习Qt与Web地图集成的中高级开发者。资源不仅给出了可运行的示例工程还清晰展示了QWebEngineView加载本地HTML、地址栏URL变化触发JavaScript、百度地图BMap.Point定位及Marker标记添加的方法同时附带pro配置可直接用于个人项目迁移。进一步可基于此扩展地址自动补全、右键拾取坐标等交互功能是理解C/HTML/JavaScript混合编程的高性价比样例。1. QT里嵌地图HTML这个需求到底在拆什么现在做上位机和设备监控的十有八九会遇到一个需求界面里嵌一张活地图能定位、能打点、还能跟业务数据联动。标题里这条——Ubuntu下QT调用百度地图的HTML文件在界面显示并支持输入经纬度定位——看起来像是个简单Demo实际拆下来是三段技术栈的拼接QT的WebEngine模块负责渲染HTMLHTML里跑地图JS API再靠QWebChannel把C和JavaScript的调用打通。适合正在做设备定位、车载终端、工厂资产可视化这类项目的人看也适合刚接触Qt WebEngine但不想一上来就啃官方文档的开发者直接抄作业。我拆过一个模拟项目X完整走了一遍这套链路踩过的坑都记在后面跟着做基本能一次跑通。2. 环境搭建与最小加载Ubuntu上先跑起来再谈通信2.1 版本选型Qt 5.12 与 QWebEngine 为什么是基线先说选型。QWebEngine模块在Qt 5.4之后才稳定5.12是个分水岭——从5.12开始Chromium内核版本升级到61以上HTML5特性支持才够完整百度地图JS API里的WebGL渲染、Canvas覆盖物在旧内核上会出各种奇怪问题。如果你还在用5.9甚至5.6我建议先升版本否则后面调地图的3D缩放和热力图会做不下去。Ubuntu下安装直接走apt会省很多事。注意不要只装qtbase必须把webengine相关包一起装上sudo apt update sudo apt install qtbase5-dev qtwebengine5-dev libqt5webenginewidgets5 libqt5webchannel5-dev libqt5webenginecore5装完用qmake检查一下模块路径能搜到webengine就说明包齐了qmake --version ls /usr/lib/x86_64-linux-gnu/cmake/ | grep Qt5WebEngine如果你的系统是Ubuntu 20.04及以上apt源里的Qt版本基本都在5.12以上直接用就行。老系统或者想用更新的5.15 LTS建议装离线安装包别折腾编译Chromium编译一次够你喝一壶的。2.2 pro文件配置与最小工程一个能加载本地HTML的骨架工程文件里必须显式声明webenginewidgets和webchannel两个模块network模块后面调坐标转换接口用得到提前声明省得后期加QT core gui webenginewidgets webchannel network TARGET MapDemo TEMPLATE app SOURCES main.cpp bridge.cpp HEADERS bridge.h RESOURCES res.qrcmain.cpp里最基础的加载逻辑长这样重点我写在注释里#include QApplication #include QWebEngineView #include QWebEngineProfile #include QWebEngineSettings int main(int argc, char *argv[]) { // 这一行必须在QApplication创建之前设置否则部分机器上WebEngine会闪退 QApplication::setAttribute(Qt::AA_ShareOpenGLContexts); QApplication app(argc, argv); QWebEngineView *view new QWebEngineView; view-page()-settings()-setAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls, true); view-page()-settings()-setAttribute(QWebEngineSettings::JavascriptCanAccessClipboard, true); // 加载本地HTML注意一定要用绝对路径或qrc路径 view-load(QUrl::fromLocalFile(/home/user/projects/MapDemo/map.html)); view-resize(1024, 700); view-show(); return app.exec(); }这里有两个细节值得说明。第一LocalContentCanAccessRemoteUrls这个属性决定了本地HTML能不能请求外网的地图JS和瓦片服务不打开的话地图只会显示灰屏第二加载本地文件务必用fromLocalFile直接用setUrl(file:///)在某些Qt版本上会因为路径解析不一致触发加载失败这是我排查过两次的翻车点。2.3 加载方式选择开发用 file 路径发布用资源打包开发阶段我会建议直接加载本地绝对路径改完HTML按个F5就生效效率最高。但发布给现场用的时候HTML文件不能裸放在工程目录下得打进qrc资源里规避路径丢失问题。qrc资源文件里这样登记RCC qresource prefix/ filehtml/map.html/file filehtml/qwebchannel.js/file /qresource /RCC对应的加载代码改成view-load(QUrl(qrc:/html/map.html));提一句资源压缩的细节qrc默认用zlib压缩HTML文件本身就是文本压缩后体积很小但如果你后期加了离线地图切片包别往qrc里塞资源文件在Windows上会因文件句柄占用导致重编译失败离线瓦片直接放外部目录更稳妥。3. HTML与地图JS层初始化地图与经纬度定位函数3.1 HTML骨架引入地图API并初始化容器地图HTML是整套方案的中枢C那边只是壳真正的画面渲染、覆盖物管理、事件监听全在这一个文件里。先看骨架!DOCTYPE html html head meta charsetutf-8 title地图定位面板/title meta nameviewport contentwidthdevice-width, initial-scale1.0 style html, body, #map { width: 100%; height: 100%; margin: 0; padding: 0; } /style !-- 地图JS SDKak换成你申请的密钥 -- script srchttps://api.map.baidu.com/api?typewebglv1.0ak你的密钥/script /head body div idmap/div /body /html这里有个选择要讲清楚地图加载方式有typewebgl和默认的typejavascript两种webgl模式渲染性能更好3D缩放更平滑但对Chromium版本要求更高——Qt 5.12自带的Chromium 61跑webgl模式勉强能用如果项目里用的OpenGL驱动不完整会直接黑屏。保守做法是用默认2D模式稳定优先。3.2 地图初始化中心点、缩放级别和控件地图对象必须在页面DOM就绪后创建我一般这样初始化var map; function initMap() { map new BMap.Map(map); // 绑定容器 map.centerAndZoom(new BMap.Point(116.404, 39.915), 12); // 默认中心和缩放级 map.enableScrollWheelZoom(true); // 滚轮缩放现场演示必备 // 控件按需加不要全堆上 var nav new BMap.NavigationControl({anchor: BMAP_ANCHOR_TOP_LEFT, type: BMAP_NAVIGATION_CONTROL_ZOOM}); var scale new BMap.ScaleControl({anchor: BMAP_ANCHOR_BOTTOM_LEFT}); map.addControl(nav); map.addControl(scale); } document.addEventListener(DOMContentLoaded, initMap);CenterAndZoom的缩放级别默认值是10到18室外设备监控我一般用14到16能看到具体街道如果是厂区资产分布图12到13更合适能容纳更多点位不重叠。别忘了enableScrollWheelZoom开发机上鼠标滚轮操作会非常顺手。3.3 经纬度定位函数C调用的JS入口标题里的“输入经纬度定位”落地到JS就是一个全局函数C derece通过QWebChannel调用它。这个函数要处理三类输入非法值、未初始化、正常定位function locateTo(lon, lat) { if (typeof map undefined || map null) { console.log([JS] map not ready yet); return; } if (isNaN(lon) || isNaN(lat) || lon -180 || lon 180 || lat -90 || lat 90) { console.log([JS] invalid lonlat: lon , lat); return; } var point new BMap.Point(lon, lat); map.centerAndZoom(point, 17); // 同时放一个标记点方便确认定位结果 map.clearOverlays(); var marker new BMap.Marker(point); map.addOverlay(marker); map.panTo(point); }有两点要说。一是clearOverlays()会清掉所有覆盖物包括你在业务里加的圈选、轨迹线如果项目里有多层数据建议改成标记数组手动维护二是centerAndZoom同时设置中心和缩放级别比分别调用setCenter和setZoom省一次重绘地图闪烁感弱很多。4. QWebChannel双向通信C调JS、JS回C的完整链路4.1 为什么不直接用 runJavaScript 硬调很多人第一反应是直接在C侧用page()-runJavaScript(locateTo(...))这确实能跑通C到JS的单向调用。但需求一旦变成“用户点击地图取点把坐标回传给C”runJavaScript的回调写法就非常别扭还要处理异步嵌套。QWebChannel是把一个C对象注入到JS上下文里JS那边可以当成本地对象一样调用它的槽函数、连接它的信号反过来C也能通过同样的通道调JS函数。双向通信一套代码解决。4.2 C侧定义桥接类并注册到通道先写一个桥接类承载C暴露给JS的能力// bridge.h #ifndef BRIDGE_H #define BRIDGE_H #include QObject #include QDebug class MapBridge : public QObject { Q_OBJECT public: explicit MapBridge(QObject *parent nullptr); public slots: // JS调用这个槽把底图上点击的坐标传回C void onJsPoint(double lon, double lat); signals: // 从C发送给JS的定位信号其实是通过WebChannel统一管理 void jsLocate(double lon, double lat); private: double m_lastLon; double m_lastLat; }; #endif // BRIDGE_H// bridge.cpp #include bridge.h MapBridge::MapBridge(QObject *parent) : QObject(parent) { m_lastLon 0; m_lastLat 0; } void MapBridge::onJsPoint(double lon, double lat) { m_lastLon lon; m_lastLat lat; qDebug() Get point from JS: lon lat; }然后回到main函数里把通道挂上。这里有个关键步骤WebChannel要使用QWebEnginePage的webChannelTransport#include QWebChannel #include bridge.h // 在main函数中继续略去重复部分 MapBridge *bridge new MapBridge(view); QWebChannel *channel new QWebChannel(view-page()); channel-registerObject(bridge, bridge); // 注册名后面JS要用不能改 view-page()-setWebChannel(channel); // 从C主动定位一个坐标测试通道 QTimer::singleShot(3000, []() { emit bridge-jsLocate(116.405, 39.909); });registerObject第一个参数是JS侧可见的对象名这里叫bridgeJS那边的channel.objects.bridge必须和它一致大小写错一个字母都是undefined这种错误报错不明显排查起来最费时间。4.3 JS侧引入 qwebchannel.js 并建立通道本地HTML要引入qwebchannel.js脚本这个文件在Qt安装目录里能找到find /usr/lib -name qwebchannel.js 2/dev/null找到后把它复制到工程html目录下或直接用qrc打包。然后在HTML里建立通道// map.html 中新增 script srcqwebchannel.js/script script var bridge; // document ready 后建立通道 window.onload function() { if (typeof qt ! undefined) { new QWebChannel(qt.webChannelTransport, function(channel) { bridge channel.objects.bridge; console.log([JS] QWebChannel ready); // 如果C侧已经初始化完成可以主动预告 bridge.jsLocate.connect(function(lon, lat) { locateTo(lon, lat); }); }); } }; /script通道建立的时机很讲究window.onload比DOMContentLoaded晚一步此时地图对象已经初始化完毕locateTo可以被正常调用不会报“map not ready”。4.4 通道时序先等页面再等channel再发定位整个项目的时序如果理不清最容易出现“C调用没反应但也不报错”的玄学问题。我把推荐时序写在这里// 用lambda把加载完成和通道建立串起来 connect(view, QWebEngineView::loadFinished, this, [](bool ok) { if (!ok) return; QTimer::singleShot(500, []() { // 500ms是为了留出QWebChannel初始化时间 emit bridge-jsLocate(116.404, 39.915); }); });实测在普通配置的i5机器上QWebChannel从页面加载到对象可用大约需要300到600ms所以定时器延时500ms是合理值。如果页面里地图SDK加载慢就把延时调到1000ms多等一秒不会显得卡顿但能避开80%的通道未就绪问题。5. 常见问题与避坑六个我踩过的地图嵌入坑5.1 现象地图区域灰屏控制台无报错第一次跑通时最容易遇到的就是灰屏。原因有两个可能性叠加一是LocalContentCanAccessRemoteUrls没有置true本地文档访问外域JS被Quinn的web安全策略拦截二是编码问题Ubuntu默认UTF-8如果HTML文件用GBK保存地图API加载时解析出错。解决确保pro和代码中都设置过该属性同时把HTML用UTF-8无BOM重新保存。用命令行验证最直接——在浏览器中打开该HTML如果能显示地图说明问题在Qt侧的权限配置。提示页面加载完成后用view-page()-toHtml()或者开启webEngine的远程调试端口检查实际渲染结果比自己猜要高效得多。5.2 现象C调用locateTo后地图无反应但JS里手动执行正常这是典型的通道未建立问题。loadFinished只代表HTML加载完毕不代表QWebChannel已经注册成功。JS侧console如果没打印QWebChannel ready说明qt.webChannelTransport还是undefined。解决延后调用或者在JS侧增加就绪回调后让JS主动通知C。推荐第二种更稳健window.onload function() { if (typeof qt ! undefined) { new QWebChannel(qt.webChannelTransport, function(channel) { bridge channel.objects.bridge; // 通知C侧通道就绪 bridge.channelReady(); }); } };对应在C桥接类里加一个槽channelReady收到后置一个标志位再执行真正的定位逻辑。这样彻底消除时序猜测。5.3 现象JS返回的坐标在地图上偏了几百米同一组经纬度手机上打开地图是准的放到这个系统里就偏移这个坑在对接GPS时几乎必踩。原因是地图服务使用的坐标系不是标准的WGS84GD和BD都有加密偏移转换其中BD-09相对WGS84在北京市区大约偏移200到600米且不是线性固定量。解决在C侧用QM网络请求调坐标转换服务统一在进入地图前转换QNetworkAccessManager *manager new QNetworkAccessManager(this); QString url QString(https://api.map.baidu.com/geoconv/v1/?coords%1,%2from1to5ak你的密钥) .arg(lon).arg(lat); manager-get(QNetworkRequest(QUrl(url)));from1代表GPS原始坐标to5代表地图坐标。转换后取JSON里result数组的第一组数据再传给前端这才是最终能用的坐标。from和to的参数表如下参数含义from1GPS设备原始坐标from2某地图商坐标to5地图显示坐标to6某地图商下一步预留坐标5.4 现象高DPI屏幕上地图模糊现在高分屏普及如果QWebEngineView默认按逻辑分辨率渲染地图会有明显锯齿。解决方法是设置缩放因子让分辨率匹配view-setZoomFactor(1.5);或者更优雅的做法是检测设备像素比后动态调整int dpr qApp-devicePixelRatio(); view-setZoomFactor(dpr 1 ? dpr : 1.0);这个细节在项目交付验收时经常被客户揪出来提前处理省得后期改。5.5 现象发布到工控机后地图显示正常但中文标注全部是方块工控机上往往没装中文字体chromium字体回退失败导致豆腐块。解决方式不是让客户装字体而是在启动时指定字体路径export QTWEBENGINE_CHROMIUM_FLAGS--font-render-hintingnone同时在系统层装一个思源黑体包sudo apt install fonts-noto-cjk装完杀掉进程重开中文标注就能正常渲染。5.6 现象嵌入多个地图页面时内存飙升有的项目在一个界面里同时放两个QWebEngineView内存直接翻倍。原因是每个View默认独立渲染进程。如果两个页面是同一地图底座建议共用一个ProfileQWebEngineProfile *profile new QWebEngineProfile(mapProfile, view); profile-setParent(view); QWebEnginePage *page new QWebEnginePage(profile, view); view-setPage(page);这样Chromium会复用同一进程组内存能省30%左右代价是两个页面不能各自使用完全不同的localStorage数据。工控机内存只有4GB时会比较明显务必测试。6. 进阶验证鼠标取点、轨迹回放与发布检查6.1 鼠标点击取点把用户的点击坐标送回C地图上业务操作最常见的就是点击某个位置然后把坐标给C后续逻辑处理。在HTML里给地图绑定单击事件map.addEventListener(click, function(e) { var point e.point; if (bridge) { bridge.onJsPoint(point.lng, point.lat); } });对应C侧的bridge槽函数里把lon、lat字符串格式化输出给上层逻辑或者直接入库。注意e.point和e.target的区分——取坐标用e.point取覆盖物对象用e.target混用会拿到undefined属性不报错但坐标永远是空值。6.2 轨迹回放把连续定位点画成折线设备监控场景常需要展示历史轨迹。这次不要用clearOverlays加上轨迹图层function drawTrack(locations) { map.clearOverlays(); var points []; for (var i 0; i locations.length; i) { var item locations[i]; points.push(new BMap.Point(item[0], item[1])); } var polyline new BMap.Polyline(points, { strokeColor: #2E7D32, strokeWeight: 4, strokeOpacity: 0.8 }); map.addOverlay(polyline); map.setViewport(points); // 自动调整视野到轨迹范围 }注意setViewport一次调用就完成缩放和平移比手动计算最大最小经纬度再centerAndZoom快很多而且视野正好容纳所有点。6.3 发布检查清单交付前我必查的五个点每次做这类项目交付前我会强制过一遍验证清单防止现场翻车1. 无外网环境测试地图JS SDK离线包是否准备、瓦片是否缓存 2. Root权限运行QTWEBENGINE_CHROMIUM_FLAGS是否需要追加 --no-sandbox 3. 多屏显示地图在副屏上是否分辨率异常 4. 快速重复点击定位清空覆盖物后是否有内存泄漏 5. 断网重连地图加载失败后是否自动重试尤其第一条现场没有外网是常态就算不能在线加载瓦片也至少要保证程序不崩溃、能提示网络异常而不是白屏。6.4 调试技巧远程调试与console日志捕获QWebEngine支持远程调试断点时非常好用。启动时设置qputenv(QTWEBENGINE_REMOTE_DEBUGGING, 9222);然后从本机浏览器打开http://127.0.0.1:9222能看到当前WebEngine渲染的完整DOM结构和console输出。这在排查JS报错时比在C里一顿printf高效太多。习惯上我每做一款新版本地图集成都会把远程调试默认开启等交付时再关掉防止客户误连调试端口带来额外风险。这套方案我从最初的纯runJavaScript调用演进到统一走QWebChannel双向通信之后代码维护成本低了不少。从那以后凡是遇到Qt嵌地图类需求我都强制把通道就绪标志、坐标转换接口、远程调试开关这三样先搭进去再开始写业务省下的排障时间足够再喝一杯。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑