• Qt开发北斗定位系统融合百度地图API及Qt程序打包发布


    Qt开发北斗定位系统融合百度地图API及Qt程序打包发布

    1、上位机介绍

    最近有个接了一个小型项目,内容很简单,就是解析北斗GPS的串口数据然后输出经纬度,但接过来觉得太简单,就发挥了主观能动性,增加了百度地图API,不但能实时定位,还能在地图上标识出位置信息,用的QT5.5。上位机运行图片如图所示:整体运行比较流畅。

    上一个版本的界面

    windows版本界面

    windows卫星图图片

    Linux版本界面

    底层设别

    原理就是界面上集成一个WebKits/WebView,让Qt和Javascript进行交互。但需要注意Qt5.6以上版本取消了WebView的模块,换成了webenginewidgets,看上去配置好麻烦,甚至还要自己编译什么的,虽然性能可能有指数性的提升,但对于我这个做嵌入式软件和硬件,上位机会个基础的就算是很好的人来说,还是webkits好一点。

    2017/07/22 更新:


    在本文后续版本已经适配了Qt 5.6 以上版本的QWebEngine版本,摒弃了QWebKits组件,后续若有经费的话,将继续更新支持QWebChannel通信通道。两个版本都可以在本文尾部的附件中下载,欢迎学习讨论。


    2. 开发介绍

    本设计开发主要涉及三个方面:

    • 串口开发(北斗GPS基于UART的,波特率115200,8,1),这个北斗GPS模块隔1s发一次GPS数据组,会通信几个卫星接收数据,时而一些卫星不反馈数据。
    • 数据解析。数据解析模块包括把几个卫星的数据按协议分开然后解析出来,这里有个难点在于Qt串口和CH340缓存BUG导致的数据包粘连和数据不连续解决。
    • 地图API驱动

    2.1 串口开发

    串口开发不用说了,请参考我前几篇有个蓝牙的博客,上面有源码,Qt on Android 蓝牙开发,本设计中的串口部分就是基于那个串口开发的。串口开发自动检测连接设备,不需要进入管理器和找到COM口是多少,自动和串口进行连接。

    2.2 数据解析

    串口数据粘包和数据不连续很头疼,进入一个串口接收槽函数QString rxArray.append(serialPort->readAll() ); 接收数据不完整,或者说会分好几次进行接收,而且分好几次接收长度没有规律,所以无法直接使用接收的数据。

    1S钟GPS发送一次数据为:

    /*
    $GNRMC,114821.880,V,3957.378130,N,11620.848015,E,0.000,0.000,230417,,E,N*23
    $GNGGA,114821.880,3957.378130,N,11620.848015,E,0,00,127.000,100.800,M,0,M,,*6D
    $GNGLL,3957.378130,N,11620.848015,E,114821.880,V,N*52
    $GNGSA,A,1,,,,,,,,,,,,,127.000,127.000,127.000*2A
    $GNGSA,A,1,,,,,,,,,,,,,127.000,127.000,127.000*2A
    $GPGSV,1,1,4,17,57,315,21,22,35,67,,28,75,176,,30,12,204,*74
    */
    

    数据量比较巨大,所以这里增加处理机制,尽量保存完整数据。

    // 接收数据槽函数
    
    void Widget::RxData(){
    
    
        QString rxString;
    
        rxArray.append(serialPort->readAll());
        //qDebug() << QString(rxArray);
        if( serialRead == true ){
            // 数据对齐,如果上次数据是一半,抛弃数据,重新接受
            times++;
            rxString = QString(rxArray);
            //qDebug() << "rec:" << rxString;
            ui->textBrowser->append(tr("--------------------------------------------------------------------------"));
            ui->textBrowser->append("从北斗GPS传感器第("+QString::number(times)+")次接受数据:");
            ui->textBrowser->append(tr("--------------------------------------------------------------------------"));
            ui->textBrowser->append(QString(rxArray));
            gpsDatasProcessing( rxArray );
            rxArray.clear();
            serialRead = false;
            if( times%50 == 0 ) {
                ui->textBrowser->clear();
            }
            ui->textBrowser->append(tr("--------------------------------------------------------------------------
    "));
        }else{
            return;
        }
        // 解析数据
    
    }
    void Widget::gpsDatasProcessing(QByteArray GPSBuffer)
    {
    
        QString GPSBufferString = QString( GPSBuffer );
        int error_pos = 0;
        QString GNRMC_String = NULL;
        QString GPGGA_String = NULL;
        QString GPGSV_String = NULL;
        QString GPRMC_String = NULL;
        QString GPGLL_String = NULL;
        QString GNGGA_String = NULL;
        bool latiflag = false;
        bool atiflag = false;
        bool utcflag = false;
        bool speedflag = false;
        bool longtiflag = false;
    
        QList<QString> gpsStringList = GPSBufferString.split('
    ');
    
    
        // 由于定时间隔,数据包发生黏连,纠正数据。
        if( gpsStringList.at(0).at(0) != '$' ) {
            QString ErrorString =  gpsStringList.at(gpsStringList.length()-1) + gpsStringList.at(0);
            error_pos = 1;
            if( ErrorString.contains("$GNRMC") ){
                GNRMC_String = ErrorString;
            }else if( ErrorString.contains("$GPGGA") ) {
                GPGGA_String = ErrorString;
            }else if( ErrorString.contains("$GPGSV")  ) {
                GPGSV_String = ErrorString;
            }else if( ErrorString.contains("$GPRMC") ) {
                GPRMC_String = ErrorString;
            }else if( ErrorString.contains("$GPGLL") ) {
                GPGLL_String = ErrorString;
            }else if( ErrorString.contains("$GNGGA") ) {
                GNGGA_String = ErrorString;
            }
    
        }else{
            error_pos = 0;
        }
        // 从QList中得到数据
        for( int i = error_pos; i < gpsStringList.length()- error_pos; i++ ) {
            if( gpsStringList.at(i).contains("$GNRMC") ){
                GNRMC_String = gpsStringList.at(i);
            }else if( gpsStringList.at(i).contains("$GPGGA") ) {
                GPGGA_String = gpsStringList.at(i);
            }else if( gpsStringList.at(i).contains("$GPGSV")  ) {
                GPGSV_String = gpsStringList.at(i);
            }else if( gpsStringList.at(i).contains("$GPRMC") ) {
                GPRMC_String = gpsStringList.at(i);
            }else if( gpsStringList.at(i).contains("$GPGLL") ) {
                GPGLL_String = gpsStringList.at(i);
            }else if( gpsStringList.at(i).contains("$GNGGA") ) {
                GNGGA_String = gpsStringList.at(i);
            }
        }
        if( !GPGGA_String.isNull() ) {
            QList<QString> gpggaStrList = GPGGA_String.split(",");
            QString utcstr = gpggaStrList.at(1);
            ui->lineEdit_UTC->setText("格林威治时间:"+utcstr.mid(0,2)+":"+utcstr.mid(2,2)+":"+utcstr.mid(4,2));
            QString latistr = gpggaStrList.at(2);
            ui->lineEdit_latitude->setText("北纬"+latistr.mid(0,2)+"度"+latistr.mid(2,7)+"分");
            QString altistr = gpggaStrList.at(4);
            ui->lineEdit_longitude->setText("西经"+altistr.mid(0,3)+"度"+altistr.mid(3,7)+"分");
            utcflag = true;
            latiflag = true;
            atiflag = true;
        }
        if( !GNGGA_String.isNull() ) {
            if( !latiflag ) {
                QList<QString> gnggaStrList = GNGGA_String.split(",");
                QString utcstr = gnggaStrList.at(1);
                UTC2BTC(&utcstr);
                ui->lineEdit_UTC->setText("北京时间:"+utcstr.mid(0,2)+":"+utcstr.mid(2,2)+":"+utcstr.mid(4,2));
                QString latistr = gnggaStrList.at(2);
                ui->lineEdit_latitude->setText("北纬"+latistr.mid(0,2)+"°"+latistr.mid(2,9)+"'");
                double double_lati = latistr.mid(0,2).toDouble()+(latistr.mid(2,7).toDouble()+0.25)/60;
                QString altistr = gnggaStrList.at(4);
                ui->lineEdit_longitude->setText("西经"+altistr.mid(0,3)+"°"+altistr.mid(3,9)+"'");
                double double_alti = altistr.mid(0,3).toDouble()+(altistr.mid(3,7).toDouble()+0.25)/60;
    
                setCoordinate(QString::number(double_alti),QString::number(double_lati));
                //setCoordinate(QString::number(108.886119),QString::number(34.223921));
                qDebug()<< "纬度:"<<QString::number(double_alti)<<"|"<<"经度:"<< QString::number(double_lati) << "
    ";
                QString longtistr = gnggaStrList.at(9);
                ui->lineEdit_altitude->setText(longtistr+"m ");
                ui->lineEdit_speed->setText("无效PPS");
    
                utcflag = true;
                latiflag = true;
                atiflag = true;
    
            }
        }
    
    }
    void Widget::slotSerialTimerOut()
    {
        if( serialRead == false ){
            serialRead = true;
        }
    }
    

    2.3 百度地图API

    百度地图API我找了很多资料,参考资料本文附录,非常感谢博客名“灿哥哥”,“我是大坏蛋”的整理,“灿哥哥”在文章中不但提供了离线地图和方法,还提供了对于地图的基本介绍,对于地图上面的操作,请参考灿哥哥的博客。但就我开发我想提出两点:

    • 本设计使用的是离线地图,地图包30M左右,不联网也可以使用。把地图包,放在编译的release或者debug文件夹下,载Qt主程序中的url协商地图html的位置。
    • 如果使用的是在线地图就需要连接网络,最重要的是坐标转换,经纬度需要和百度地图坐标进行一个转换,这个转换是通过百度地图API的接口进行的,而且普通用户转换还有次数限制。  
    • 这个离线地图不需要转换,直接使用经纬度就可以定位。

    下面代码可以看到Qt和Javascript如何互动的。

    void Widget::getCoordinate(QString lon,QString lat)
    {
        QString tempLon="鼠标经度:"+lon+"°";
        QString tempLat="鼠标纬度:"+lat+"°";
        ui->labelMouseLongitude->setText(tempLon);
        ui->labelMouseLatitude->setText(tempLat);
    }
    
    void Widget::setCoordinate(QString lon,QString lat)
    {
        QWebFrame *webFrame = ui->webView->page()->mainFrame();
        QString cmd = QString("showAddress("%1","%2")").arg(lon).arg(lat);
        webFrame->evaluateJavaScript(cmd);
    }
    void Widget::on_pushButtonStreetMap_clicked()
    {
        QWebFrame *frame = ui->webView->page()->mainFrame();
        QString cmd = QString("showStreetMap()");
        frame->evaluateJavaScript(cmd);
        ui->pushButtonSatelliteMap->setEnabled(true);
        ui->pushButtonStreetMap->setEnabled(false);
    }
    
    void Widget::on_pushButtonSatelliteMap_clicked()
    {
        QWebFrame *frame = ui->webView->page()->mainFrame();
        QString cmd = QString("showSatelliteMap()");
        frame->evaluateJavaScript(cmd);
        ui->pushButtonSatelliteMap->setEnabled(false);
        ui->pushButtonStreetMap->setEnabled(true);
    }
    
    void Widget::slotPopulateJavaScriptWindowObject()
    {
        ui->webView->page()->mainFrame()->addToJavaScriptWindowObject("ReinforcePC", this);
    
    }
    

    3. 程序打包

    做完这个程序之后呢,我开始研究如何给程序打包,终于在两个小时内搞定了。

    Step1:把所需要的dll文件集成出来。

    Step2:用工具把dll文件exe文件和其他文件封装起来,做成msi或者exe文件。

    工具就是一个Qt自带的windeployqt工具,另一个是Advanced installer安装包打包程序。

    3.1 搜集dll文件

    1)在开始菜单找到Qt文件夹,里面有个像是cmd命令行一样的东西,我的是MinGW的。反正运行出来这个样子:

    搜集dll的cmd

    2)进入Qt的工程文件夹,在release或者debug里面(看你用的是release编译还是debug编译了),找到生成的exe文件夹,把这个exe文件复制到一个方便找,方便输入路径的地方。我放在了D:setup文件里了。

    3)在刚才出那个命令行里面输入: cd /d D:setup 切换到这个文件夹。

    4)输入命令: windeployqt xxx.exe xxx.exe就是你刚才编译出exe的名字。

    然后你会发现Qt把所有这个exe文件需要的.dll文件和其他支持库文件都放在这个文件夹了。实际上到了这步你可以打成压缩包然后发布到任何电脑,解压直接运行

    3.2 使用Advanced Installer打包程序

    我还是比较喜欢把他弄成安装包,这样更方便,更正式。Advanced Installer这个工具简直太赞了,打包的程序安装界面十分的正式,一点都不山寨,还有很多安装包的皮肤可供选择。如图为打好包的图标:

    打包后的程序

    运行后的效果如图:

    img

    里面还提供了导入注册表、安装后创建快捷方式等等方便的配置。

    ** 下载地址:http://down7.pc6.com/gm1/Advanced Installer.zip **

    使用教程,还是参考后面的参考文献中的【4】,这里不在赘述了。

    本文附件:

    [1] 本程序旧版安装包下载地址如下百度云盘地址 提取码:41hx (2017-04-29)

    [2] 本程序新版安装包下载地址如下:百度云盘地址 提取码:o59r (2017-07-22更新)

    参考文献:

    [1] 我是大坏蛋,gps定位Qt界面百度地图api的介绍,CSDN博客,2014-08-24
    [2] 灿哥哥,Qt加载百度离线地图,CSDN博客,2016-03-30
    [3] winland0704,Qt官方开发环境生成的exe发布方式--使用windeployqt,Qt百度贴吧,2015-04-28
    [4] Prodesire,Windows安装包制作指南-Advanced Installer的使用,cnBlogs博客,2016-08-18
    [5] jwq2011的专栏,GPS数据包格式+数据解析,CSDN博客,2016-12-15

    版权声明:

    1· 本文为MULTIBEANS团队研发跟随文章,未经允许不得转载。

    2· 文中涉及的内容若有侵权行为,请与本人联系,本人会及时删除。

    3· 尊重成果,本文将用的参考文献全部给出,向无私的工程师,爱好者致敬。


  • 相关阅读:
    带修改离线主席树 + 树状数组 ZOJ
    树上主席树 + LCA SPOJ
    基础静态主席树 POJ
    数列分块入门1-9 LibreOJ
    Some about me
    [Java]Thinking in Java 练习2.12
    [Java]Thinking in Java 练习2.10
    [Java]Java中的自动包装
    [Java]Thinking in Java 练习2.2
    [杂记]CodeBlocks下载、安装及设置
  • 原文地址:https://www.cnblogs.com/sigma0/p/7220334.html
Copyright © 2020-2023  润新知