Qt嵌入百度地图API的详细流程与常见问题

9 篇文章 0 订阅
2 篇文章 0 订阅

关于Qt应用嵌入地图这一块 我是好久之前就开始做了,前段时间测试的时候貌似是没问题了,可刚拉出去溜溜时就出BUG了。。。在查找BUG时发现几个坑,不小心的话还是很容易掉进去的,所以想着记录下来和大家分享一下 。关于创建这一块我也没写过,那就从工程建立开始——


0 准备工作

开发环境 Qt5.7 MSVC2015
(我刚开始接触Qt时使用的MinGW版本 后来就是要嵌入网页时发现MinGW好像没有QWebEngine模块)

需要文件

  1. 地图文件 BDMap.html
  2. 连接工具 QwebChannel.js

其中 地图文件是要自己创建,QwebChannel.jsQt自带的,不需要任何修改,拷贝到地图文件同一层目录即可。文章最后会附上项目链接,项目中可以找到。

基本思路

  1. 在界面中要创建网页容器QWebEngineView类,这是Qt提供的继承于QWidget类的一个窗口,使用时在.Pro文件中要添加QT += webenginewidgets
  2. 创建QtHtml的连接通道QWebChannel,通过该通道可以实现主程序与网页之间的通信。当然,通道的建立是双方面的,即主程序和网页中都要进行相关操作;
  3. 通道建立完成即可实现主程序与网页之间的函数相互调用,并由此实现参数传递。

一、工程建立

  1. 建立一个主窗口项目,名称为BDMap,项目文件中添加QT += webenginewidgets,UI界面如下图所示:
    在这里插入图片描述
    其中MapWidgetQWidget提升而成:
    在这里插入图片描述

  2. 右键项目,添加自定义bridge类,继承于QObject,在主窗口中包含头文件并创建对象JSBridge

  3. 在项目文件夹下新建文件夹Baidu_JS,并将BDMap.html
    qwebchannel.js拷贝到该文件夹下;在右击项目添加资源文件map,并将BDMap.htmlqwebchannel.js导入其中。
    (网页文件可以不作为资源使用,不过要指定文件路径,当程序发布到别的电脑使用时,网页文件要跟着拷贝过去)
    至此,项目创建完成,文件结构为:
    在这里插入图片描述


二、程序编写

  1. bridge类作为主程序和网页之间的"桥梁",起到从网页接收数据的作用。不妨定义一个公有槽函数:

    void bridge::RcvPoint(const QString &lng, const QString &lat)
    {
        qDebug()<<lng<<","<<lat;
    }
    

    即接收地图传来的坐标并打印。

  2. 主窗口构造函数:

    MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),
    ui(new Ui::MainWindow)
    {
        ui->setupUi(this);
        bridge *JSBridge = new bridge(this);
        QWebChannel *channel = new QWebChannel(this);
        channel->registerObject("window",(QObject*)JSBridge);
        ui->MapWidget->page()->setWebChannel(channel);
        ui->MapWidget->page()->load(QUrl("qrc:/Baidu_JS/BDMap.html"));
    }
    

    首先分别创建bridge对象JSBridgeQWebChannel对象channel

    然后将JSBridge注册到channel中,注册的名称为window

    接着将通道channel添加到网页中

    最后加载网页,完成。

3.主窗口通道建立完成之后就轮到html中了。在html中首先加载qwebchannel.js工具。

<script src="qwebchannel.js"></script>

(再说一遍,.js要和.html放在同一层目录)
接着,可以在地图初始化完成后创建通道:

 new QWebChannel(qt.webChannelTransport, function(channel) {
     mw = channel.objects.window;
 });	

这样,就可以在后面调用我们在bridge中定义的槽函数RcvPoint(lng,lat)了:

mp.addEventListener("click",function(e){
        mw.RcvPoint(e.point.lng,e.point.lat);
 });

BDMap.html:

<!DOCTYPE html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<title>CanvasLayer</title>
<script type="text/javascript" src="http://api.map.baidu.com/api?v=2.0&ak=你的密钥"></script>
<style type="text/css">
body, html,#container {width: 100%;height: 100%;overflow: hidden;margin:0;font-family:"微软雅黑";}
</style>
</head>
<body>
    <div id="container"></div>
</body>
</html>
<script src="qwebchannel.js"></script>
<script type="text/javascript">
    var mp = new BMap.Map("container");
    mp.centerAndZoom(new BMap.Point(116.3964,39.9093), 10);
    mp.enableScrollWheelZoom();
    new QWebChannel(qt.webChannelTransport, function(c) {
        mw = c.objects.window;
    });
    mp.addEventListener("click",function(e){
        mw.RcvPoint(e.point.lng,e.point.lat);
    });
</script>


有话要说
1).

    new QWebChannel(qt.webChannelTransport, function(channel) {
        mw = channel.objects.window;
    });	

对于这样一个函数,里面哪些是可变的?
首先,这个函数名是qwebchannel.js内置的,不可变,第一个参数为channel的类型(或者说作用)是Qt端到Web端的一个转换通道;第二个参数是一个自定义的function(),他的参数channel是可变的,甚至可以用 c来代替,但要和下一行的保持一致;
mw相当于一个全局变量,但也是我自定义的,甚至可以用b来代替;
再看等号右边,channel说过了,要和上边一致;右边的objects是他的一个元素,不可变;再右边的window也是自定义的,但要和主窗口中JSBridge的注册名一致。

2).这样说可能还是有点不明白,但再举一个例子就清楚多了:
比如说有两个变量 int a =5;int b = 10;,现在要把这两个数交换一下应该怎么做?——最简单的做法是再定义一个int c;,然后

   c = a;
   a = b;
   b = c;

在这里边,c就是一个中间变量,只起到一个传递作用,就相当于上边的channel,我们在主程序中创建一个bridge对象,然后要注册到channel中,说白了就是在channel中保存为window(注册名),然后到html中将保存的对象取出来赋给mw,所以这个mw就是主程序中的bridge!

三、功能完善

  1. 把收到的坐标在主界面的显示框中显示
    (这部分涉及的就是不同类之间的通信问题了,我之前在多线程的创建中有详细讲过)
    一句话们就是通过信号和槽传参——bridge在接收到经纬度之后,发出一个以经纬度为参数的信号,主窗口响应信号,并在槽函数中进行显示。
  2. 在主界面的输入框中输入坐标,点击发送,地图定位到该点
    首先在html中定义一个以经纬度为参数的定位函数
    function SetPoint(lng,lat){
        mp.setCenter(new BMap.Point(lng,lat));
     }
    
    在主程序中使用runJavaScript(cmd)调用html中的函数:
     void MainWindow::on_pushButton_clicked()
     {
         QString context = ui->lineEdit_SendMsg->text();
         if(!context.contains(','))
         {
             qDebug()<<"输入格式错误";        //输入格式 经度+纬度,中间以英文逗号‘,’隔开
             return;
         }
         QString lng = context.split(',').at(0);
         QString lat = context.split(',').at(1);
     
         ui->MapWidget->page()->runJavaScript(QString("SetPoint(%1,%2)").arg(lng).arg(lat));
     }
    
    至此,项目功能基本实现。地图实现更深层功能需参照百度地图API示例百度地图JSAPI3.0参考类这两个网站。

四、排坑工作

好吧,终于到这一步了,我是真没想到前面写这么多的,过了这么久才步入正题。。。

先说说遇到的第一个问题
如果在主窗口的构造函数中直接加载地图,当电脑没有网络时,控制台会持续报错:
在这里插入图片描述
当然这些错误仅仅在控制台中显示,而且除了地图模块外其他功能不会受影响,但我有强迫症——在地图初始化之前先检测一下有没有网络连接,如果没联网就不初始化地图,这下果然没有报错了;但是有一天不小心点到了地图页面某个按钮,程序都没报错,直接没了!
后来通过Debug发现出错位置在ui->MapWidget->page()->runJavaScript()那里,才恍然大悟,**地图功能都没初始化,我却在按钮槽函数调用了html中的函数。。。**赶紧改,面前有两个选择,一是去掉网络检测;二是设置一个标志位,在每次调用runJavaScript()之前检测一下标志位,。。。。我当然选择了后者

这个问题解决了,但是新的问题又出现了——如果刚运行程序时电脑没联网,地图没初始化,中间电脑网络恢复了 我又想用地图这怎么办?重启软件——这肯定不是一个好方法,稍微好一点的方法是设一个按钮重新调用一下地图初始化函数,OK,这个问题貌似也解决了,但是——这个初始化函数只能运行一次,实测的结果是当多次运行地图加载函数ui->MapWidget->page()->load(..)时,程序会出错,控制台会持续报错

[08:53:20:219]  Uncaught TypeError: Cannot read property 'x' of undefined          qrc:/Baidu_JS/my_sample.html _line1

而且地图无法正常使用,卡顿,缩放时坐标跟着偏移,无法绘制轨迹等等,具体是什么什么原因我暂时也没搞明白。

还有一个问题 当主程序有多个坐标需要上传到地图进行标注或者绘制时,一般采用for循环的方式,感觉上没什么问题,但实际操作时会发现如果坐标不止一个,在地图上绘制的顺序是错乱的,而且有可能发过去十个坐标只标注出来五六个,一开始以为是绘制的问题,直到将发送的顺序与接收的顺序对比一下才发现接收的顺序就是错乱的,而且有丢失。这主要是因为 主程中发送相邻两个坐标的时间间隔都是在1ms之内,也就几百us,但是在地图上标注,绘制所花的时间远远大于1ms,大概是几十ms的样子,所以说如果地图端采用“接收一个、绘制一个”这样的方式的话,就会出现 第一个坐标还没处理完,接下来又接收到了好几个坐标,那这几个点来不及处理肯定就丢失了。这个问题最终采用的是信号与槽的方式解决,即主程序发送一个坐标后,地图端开始处理,等到处理完给主程序返回一个信号,主程序收到该信号后再发送下一个,这样以此类推直到最后一个
END


PS:在Qt中调试JS程序时,不像直接在Chrome浏览器调试一样,想看的信息可以直接console.log()一下,采用alert()吧又是阻塞的,有时候想看一下某个数据很难,这时候可以在bridge类中专门定义一个函数用于打印数据:

void bridge::printMessage(QString msg1, QString msg2)
{
    qDebug()<<"JS_PRINT:"<<msg1<<"   "<<msg2<<;
}

当在html中想看某个变量的值时,直接调用

mw.printMessage(data1,data2);

就可以在Qt的控制台看到了,而且显示几个参数完全可以自定义,调试时很方便。


项目链接:https://github.com/Mr-Yslf/BlogResources.git

  • 13
    点赞
  • 100
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
### 回答1: 如果你想嵌入离线的百度地图到你的Qt项目,你需要以下步骤: 1. 下载百度地图离线数据:可以到百度地图开放平台下载离线数据。 2. 导入离线数据:将下载的离线数据导入到Qt项目。 3. 加载地图:使用Qt地图组件,加载离线地图数据。 4. 显示地图:使用Qt的图形界面(GUI)元素,如QGraphicsView,在界面上显示地图。 希望这些信息能帮助您完成您的项目。 ### 回答2: 在Qt实现嵌入离线的百度地图可以通过以下步骤进行: 1. 获取百度地图离线地图数据:在百度地图开放平台上申请并下载需要的离线地图数据。离线地图数据可以作为一个文件包,包含较大范围的地理数据。 2. 创建Qt GUI应用程序:使用Qt Creator或其他开发工具创建一个Qt GUI应用程序。 3. 导入百度地图离线地图数据:将离线地图数据文件包导入到Qt项目,确保文件路径正确。 4. 创建地图显示窗口:在Qt应用程序的主窗口创建一个QWidget控件用于显示地图。QWidget控件可以包含多个子控件,用于显示地图、标记、地理坐标等。 5. 加载百度地图:使用QWebView或QWebEngineView控件在地图显示窗口加载百度地图的离线网页。在加载网页时,指定网页文件的路径,以让控件显示离线地图数据。 6. 设置地图属性:使用JavaScript与QWebView或QWebEngineView控件进行交互,设置地图显示属性,如心坐标、缩放比例等。可以使用控件提供的API或自定义JavaScript脚本。 7. 添加标记和交互:通过JavaScript与控件交互,添加标记和处理用户的事件响应。标记可以是地理点、路径、图形等。可以使用控件提供的API或自定义JavaScript脚本。 8. 编写应用逻辑:根据需要编写应用程序的逻辑,如处理地图操作、标记点击、搜索等功能。可以使用Qt提供的信号与槽机制,与地图控件进行交互。 9. 编译和运行:使用Qt Creator或其他开发工具编译和运行Qt应用程序。确保离线地图数据文件包在运行时能够正确加载显示。 通过以上步骤,在Qt实现嵌入离线的百度地图就可以实现了。可以根据具体需求,利用Qt的丰富功能和百度地图开放平台提供的API进行扩展和优化。 ### 回答3: 要使用Qt嵌入离线的百度地图,我们可以按以下步骤进行编写: 1. 下载地图数据:首先,我们需要从百度地图官网下载离线地图数据。这些数据通常以瓦片图像或矢量格式提供。我们可以选择所需的地图区域和详细级别,并下载相应的数据文件。 2. 导入地图数据:将下载的地图数据导入到我们的Qt项目。我们可以将地图数据保存在项目的本地路径,以便在需要时进行访问和加载。 3. 创建Qt窗口:在我们的Qt应用程序创建窗口,用于显示百度地图。我们可以使用Qt的QWidget或QQuickView来创建窗口。 4. 加载地图数据:在窗口加载地图数据。如果我们选择了瓦片图像格式的地图数据,我们可以使用Qt的QImage或QPixmap类来加载显示图像。如果我们选择了矢量格式的地图数据,我们可以使用Qt的绘图功能来绘制地图。 5. 实现交互功能:我们可以为地图窗口添加交互功能,例如缩放、平移和标记位置等。这可以通过捕获鼠标和触摸事件来实现。 6. 添加其他功能:如果需要,我们可以为地图窗口添加其他功能,例如搜索地点、显示标记、绘制路径等。这可以通过与百度地图API进行交互来实现。 7. 编译和运行:完成代码编写后,我们可以使用Qt编译器将项目编译为可执行文件,并在计算机上运行应用程序。 通过以上步骤,我们可以在Qt嵌入离线的百度地图,并利用Qt的功能和API来实现自定义的地图应用程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值