Qt開發北斗定位系統融合百度地圖API及Qt程序打包發布
1、上位機介紹
最近有個接了一個小型項目,內容很簡單,就是解析北斗GPS的串口數據然后輸出經緯度,但接過來覺得太簡單,就發揮了主觀能動性,增加了百度地圖API,不但能實時定位,還能在地圖上標識出位置信息,用的QT5.5。上位機運行圖片如圖所示:整體運行比較流暢。
原理就是界面上集成一個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的。反正運行出來這個樣子:
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這個工具簡直太贊了,打包的程序安裝界面十分的正式,一點都不山寨,還有很多安裝包的皮膚可供選擇。如圖為打好包的圖標:
運行后的效果如圖:
里面還提供了導入注冊表、安裝后創建快捷方式等等方便的配置。
** 下載地址: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· 尊重成果,本文將用的參考文獻全部給出,向無私的工程師,愛好者致敬。