之前在QT开发框架下调用了百度地图,调用流程请见这篇博文添加链接描述
现在是在上面实现导航定位的功能。操作主要在网页内进行。
一、实时定位
实时定位的实现主要是串口的调用,在串口实时输入GPS的坐标,显示在调用的百度地图上。
里面涉及qt程序和html文件的信息交互。
首先在QT程序里面写一个串口的类程序如下所示。
#include "serialtool.h"
#include <QSerialPortInfo>
#include <QDebug>
serialTool::serialTool()
{
global_port = new QSerialPort();
}
QStringList serialTool::getPortNameList() { // 这个是用来获取可用串口的函数
QStringList m_serialPortName; // 先定义一个QStringList用来存储串口名字
// 简化版的for循环,foreach(x1,x2):其中每次循环x1都是从x2中取出来的,相当于一个遍历的效果
// availablePorts返回所有可用的串口,但他是一个QSerialPortInfo对象,我们要做的是在foreach循环里面把他转换成QStringList列表
// 为什么要转换?答:因为availablePorts对象里面的成员变量太多了,所以把他转换成相对简洁的列表
foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts()) {
m_serialPortName << info.portName(); // 把每次遍历取到的值写入QStringList中
qDebug() << "serialportname:" << info.portName();
}
return m_serialPortName;
}
void serialTool::open_port() {
QStringList portNameList = getPortNameList(); // 获取串口列表
qDebug() << "Debug::serialTool"<< portNameList;
global_port->setPortName(portNameList[0]); // 打开第一个,一般来说只有这一个,连接多个串口的时候可能存在选择错误设备的缺陷,最好设置成固定的地址(这个自己查一下)
global_port->setBaudRate(QSerialPort::Baud115200); // 固定波特率
if(global_port->isOpen())// 打开前如果串口还打开着 先给他关闭了
{
global_port->clear();
global_port->close();
}
if(!global_port->open(QIODevice::ReadWrite)) //用ReadWrite 的模式尝试打开串口
{
qDebug()<<"打开失败!";
return;
}
qDebug()<<"串口打开成功!";
}
void serialTool::close_port() {
global_port->clear();
global_port->close();
}
QByteArray serialTool::receive_data() {
QByteArray array=global_port->readAll();
// qDebug()<<array;
static QByteArray sumData; // sumData用来做缓存的功能
static bool arrayContainsGNGGA = false;
// 第一种:数据帧包含GNGGA,说明是GPS数据帧,先保存到缓存
if (array.contains("$GNGGA")) {
sumData.append(array.mid(array.indexOf("$GNGGA")));
arrayContainsGNGGA = true;
}
// 第二种:第一种来的数据不完整,一般是经度数据存在在第一种来的帧的下一帧,
// 此时把第二帧数据存到缓存sumData中,拼接起来,再做截取处理
if (array.contains("E")&arrayContainsGNGGA) {
sumData.append(array);
arrayContainsGNGGA = false;
PasteData = sumData;
sumData.clear();
}
// 经过第一种和第二种出来的数据还有一点杂乱,这里进一步过滤
// 主要是过滤出存在下述三种的帧数据,但是又不存在/r/n这样的换行符数据,
// 符合要求的就做返回值
if (PasteData.contains("$GNGGA")&PasteData.contains("N")&PasteData.contains("E")&(!PasteData.contains("\r\n"))) {
int indexOfGNGGA = PasteData.indexOf("$GNGGA");
int indexOfN = PasteData.indexOf("N");
int indexOfE = PasteData.indexOf("E");
int len = indexOfE - indexOfGNGGA + 1;
PasteData = PasteData.mid(indexOfGNGGA, len);
prePasteData = PasteData;
// qDebug() << PasteData.mid(indexOfGNGGA, len);
return PasteData;
} else {
return prePasteData;
}
}
每个函数都有详细的注释,在此不再赘述。首先是读取串口列表,打开串口初始化,然后进行缓存。
后面的程序主要是信息的提取。在GPS中提取的数据比较杂乱,要进行数据的拼接然后进提取。
然后在主界面的程序内调用串口类。程序如下。
static bool serialButtonStatus = false; // 一个按钮包含开和关的功能,执行不同的方法
serial = new serialTool(); // 实例化一个串口类对象(这里的串口类对象是左边定义的serialTool类的实例化,你要这个不同的类里面使用它里面的一些函数,肯定要先实例化一个对象出来)
connect(ui->pushButton, &QPushButton::clicked, [=]() {
if (serialButtonStatus == false) { // false时,串口是关闭的,此时打开
serial->open_port(); // 打开串口
connect(serial->global_port,&QSerialPort::readyRead,this,&Widget::getGpsData); // readyRead监听是否来了数据,来了就执行getGpsData函数
serialButtonStatus = true;
ui->pushButton->setText("关闭导航");
} else if (serialButtonStatus == true) { // true时,串口是打开的,此时关闭
serial->close_port(); // 关闭串口
serialButtonStatus = false;
ui->pushButton->setText("打开导航");
}
这段程序主要是设置导航的按钮,点击之后,调用串口类,实例化一个串口的对象,打开串口,进行数据的接收。
void Widget::getGpsData() {
QByteArray gpsData = serial->receive_data();
double GNGGALat = 0.; // 纬度
double GNGGALon = 0.; // 经度
qDebug() << gpsData;
int firstComma = gpsData.indexOf(",");
int secondComma = gpsData.indexOf(",",firstComma+1);
int thirdComma = gpsData.indexOf(",",secondComma+1);
int forthComma = gpsData.indexOf(",",thirdComma+1);
int fifthComma = gpsData.indexOf(",",forthComma+1);
int sixthComma = gpsData.indexOf(",",fifthComma+1);
//Between third comma and fourth Comma is N or S
m_strNS = QString(gpsData.mid(thirdComma+1,forthComma-thirdComma-1));
ui->label_NS->setText(m_strNS);
//Between fifth comma and sixth Comma is E or W
m_strEW = QString(gpsData.mid(fifthComma+1,sixthComma-fifthComma-1));
ui->label_EW->setText(m_strEW);
GNGGALat = gpsData.mid(secondComma+1,thirdComma-secondComma-1).toDouble(); // 对接收过来的数据进行处理,取得GNGGA格式的经纬度数据
GNGGALon = gpsData.mid(forthComma+1,fifthComma-forthComma-1).toDouble(); // mid函数用来截取字符串,tofloat将获得的字符串转为float类型
//qDebug() << "经度:" << gpsData.mid(secondComma+1,thirdComma-secondComma-1) << firstComma << "secondComma" << secondComma << "thirdComma" <<thirdComma;
//qDebug() << "纬度" << gpsData.mid(forthComma+1,fifthComma-forthComma-1);
double gpsLon = (int)(GNGGALon/100) + (((GNGGALon/100) - (int)(GNGGALon/100))/0.6); //将GNGGA格式的经纬度数据转换成gps格式的
double gpsLat = (int)(GNGGALat/100) + (((GNGGALat/100) - (int)(GNGGALat/100))/0.6);
ui->dsb1->setValue(gpsLon);
ui->dsb2->setValue(gpsLat);
QString jsStr="addMarker("+QString::number(gpsLon)+","+QString::number(gpsLat)+")";
ui->webView->page()->runJavaScript(jsStr);
}
这段程序是在QT的UI界面内对串口内的数据进行接收,包括经纬度数据类型的转换。
实时定位的功能实现之后,就要实现定位导航的功能。
二、路径规划
首先是要在百度地图界面内添加输入起始点和终点的label,然后设置起点的按钮,设置终点的按钮。
信息都设置好之后,设置一个按键进行路径规划,此时是静态的,并不会有动态图标,但是会规划好最适宜的路径
然后设置一个开始按钮,可以显示动态的路径规划。
程序如下。
<li class="input-wrap"> <input id="x-input-start" class="input" placeholder="经度"></li>
<li class="input-wrap"> <input id="y-input-start" class="input" placeholder="纬度"> </li>
<li class = "light btn" onclick = "setStartPoint()">设置起点</li>
<li class="input-wrap"> <input id="x-input-end" class="input" placeholder="经度"></li>
<li class="input-wrap"> <input id="y-input-end" class="input" placeholder="纬度"> </li>
<li class = "light btn" onclick = "setEndPoint()">设置终点</li>
<li class = "light btn" onclick = "route()">规划</li>
<li class = "light btn" onclick = "run()">开始</li>
<li class = "night btn" onclick = "removeOverlay()">清除</li>
实现的界面如下所示。
然后提供一个供刷新和删除gps标记点的全局变量,
增加手动添加定位点的函数
fly后面的网址只是动态路径规划时的图片信息,不要因为这个而迷惑。
var preMarker // 定义一个供刷新和删除gps标记点的全局变量
var fly = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAYAAADE6YVjAAAEGUlEQVRIiZ2Wa4hVVRiGn/fbZ0YnNX+kXUwLtAgsgoJILEXEwDTtT5FFBEKkUEZC9KM/3Sn6FUiRf1MIiexillmSiE5ZEKLZhTBjssAgMbwxZ85eb6y9z5kZJ8cRF6zDZp31fe/7vfu7bB2eXjB8NSVKgpCQwEpEqUlFyzf3F75FjWIqAz4WcIAG31s+LlcXSUoUhu6UMBr02uDC1uUW1wvNxlwBHEP0A38Cx8fyMDpITWQO1sok5pZdmiXTg40KMDyCOQzaC3oHvGNUV6PI1SXxvOBxYHI+z4pUuG4T8FlmOar1ll8szD8j5Qq1bYaOmCbxvuDZDkDFJjs1Zyp5zOkRZMcBT2J9Atz4v0gOTW8MyhPJ3f1F8YHRErWpttn3WmzGHEAcxb4M6QbBcpvFHYZ1kO4dn9JSwfFONNEfBXmfKboYUONlYElNu9bI5okyvFCwG2kO8KikBcD+FF6GWAk60WFtxdxmFG8NqKBEtBA6eM24WmJxa5jdYfdU+oum8DInfZPkjULLRsqQpF0FPETZmmIVu5AuzQqUUjMplhet8vNMNxqGMBT2GlEDVA5KPy20h+CHcA3QedduP4Q9H7wvpH9LvAKcXJ93F6lck7M3u4uAnsAzwPMGQ4avLL1ZJn2czAzXmdEMsc5OyyVeFZyoATWlFbFVEdtBW4YSxXdizyN5ZqMMFgM3Yc0aTDn4FHmuxcL262mS9DDwHg1NjZa2JLzTVRZ6ouzZSPcD7wL31iiaHAWrgX1RRpqW5CmYb2W+BnYCXwCLckh1xmhdBSCewRwEXg+xvQhecK6I3IKc5tvOdp9B5Wev5ROWk36c0Z11c+BMutVmkXXYgCr2OKVFasQB4Gi+4QIa4atJqEw60s7Uj7Dvo+MjdxOpVT+EHzTMxFV6JqAEpiDNrjU3Cs3D2iHzRir8lKyNTvyFeCxTU42yCKm3IlIpTiD2A781IlUXJiWxeFjVD1WrqtO1xr1Ca2VeywVpc7utV4bZTABuO8u2TtWWfp3ePdkwNYW/NLr2XEDtujwV6CUnf+hCd0Xyc84Rj7p8yiUrgMP6eUZ3p3HlzFgxulE7CTysWZ5/7axaDvTHQBEMRLXXJ6kpj26o9s/5AFR3gpQi3m53Z2LCQEnel7TSzq6WN5QS58EZcyUpd49t3anc1GWTt45cOTS3rDTxZFexzcQdcRFQtZw+OC6lJUh9nfMquTobOBn2KvB3FxnILwGrgD63Qd3O55HroPE9hvXD599YcRlvMl4K7Bn537lAssXfQqslLUPenAusLpf/rWOStyr0gFDOzEPnuqS+q4beiUicbkQ1aArF4CcRSQuK5LtbwXVIPST3C/2O2K7wtqq5J5Mnex4dXXnGD2N1oZ9EfZifoJrx4zFNxB+50MZUEvgPAV/eU8A9QgAAAAAASUVORK5CYII='
// 手动添加定位点的函数
function movepoint(){
x = Number(document.getElementById('x-input-start').value)
y = Number(document.getElementById('y-input-start').value)
console.log(typeof(x))
var point=new BMap.Point(x,y);
var marker=new BMap.Marker(point);
map.addOverlay(marker);
因为导航定位的功能主要用于机器人等,因此采用百度地图demo中的驾车模式
程序如下
// 模拟导航功能
var myP1 = new BMap.Point(document.getElementById('x-input-start').value,document.getElementById('y-input-start').value); //起点
var myP2 = new BMap.Point(document.getElementById('x-input-end').value,document.getElementById('y-input-end').value); //终点
var myIcon = new BMap.Icon(fly, new BMap.Size(48, 48), { anchor: new BMap.Size(24, 24) })
function route(){
myP1 = new BMap.Point(document.getElementById('x-input-start').value,document.getElementById('y-input-start').value); //起点
myP2 = new BMap.Point(document.getElementById('x-input-end').value,document.getElementById('y-input-end').value); //终点
var driving2 = new BMap.DrivingRoute(map, {renderOptions:{map: map, autoViewport: true}}); //驾车实例
driving2.search(myP1, myP2); //显示一条公交线路
}
window.run = function (){
var driving = new BMap.DrivingRoute(map); //驾车实例
driving.search(myP1, myP2);
driving.setSearchCompleteCallback(function(){
var pts = driving.getResults().getPlan(0).getRoute(0).getPath(); //通过驾车实例,获得一系列点的数组
var paths = pts.length; //获得有几个点
var carMk = new BMap.Marker(pts[0],{icon:myIcon});
map.addOverlay(carMk);
i=0;
function resetMkPoint(i){
carMk.setPosition(pts[i]);
if(i < paths){
setTimeout(function(){
i++;
resetMkPoint(i);
},200);
}
}
setTimeout(function(){
resetMkPoint(5);
},1000)
});
}
确定起点和终点之后,会获得一系列的数组,获得一系列的点,进行路径轨迹的描绘。