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("--------------------------------------------------------------------------\r"));
    }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('\n');


    // 由於定時間隔,數據包發生黏連,糾正數據。
    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) << "\n";
            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%20Installer.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· 尊重成果,本文將用的參考文獻全部給出,向無私的工程師,愛好者致敬。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章