最近需要设计一个qt界面,在里面通过串口接收并解析出gps模块的经纬度信息,再通过调用高德地图的api进行地图的显示,作为入门qt的项目踩了不少坑,特将这个项目开源出来,供初学者参考学习。项目链接放下边啦,欢迎大家star~
https://github.com/daviddou2023/qt_screen_gps
一. 项目简介
本项目主要实现了电脑和L76K模块连接,模块连接天线把接收到gps数据从串口传给电脑,设计qt界面实现串口数据的接收和解析,并调用高德地图的api进行地图的3D显示。
1. 项目目录
2. 项目环境准备
(1)硬件准备
(2)软件准备
笔者采用的是Qt 5.15.2环境,因为需要用到Qt WebEngine 所以采用Desktop Qt 5.15.2 MVSC2019 64bit编译器,具体配置可参考笔者的另一篇文章https://blog.csdn.net/daviddou2022/article/details/140703079
3. 效果展示
可以看到打开串口后可以解析并显示出接收到的经纬度信息,并调用高德api进行地图显示(上图是2D显示),最终实现了3D显示。
二. 各部分代码介绍
1. project文件
QT += core gui widgets serialport webenginewidgets
QMAKE_PROJECT_DEPTH = 0
msvc {
QMAKE_CFLAGS += /utf-8
QMAKE_CXXFLAGS += /utf-8
}
CONFIG += c++17
# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
main.cpp \
mainwindow.cpp
HEADERS += \
mainwindow.h
FORMS += \
mainwindow.ui
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
这里需要注意的是因为使用到了Qt WebEngine所以需要加入以下一段代码
QT += core gui widgets serialport webenginewidgets
QMAKE_PROJECT_DEPTH = 0
msvc {
QMAKE_CFLAGS += /utf-8
QMAKE_CXXFLAGS += /utf-8
}
具体踩坑过程可以参考笔者上一篇https://blog.csdn.net/daviddou2022/article/details/140703079 说多了都是泪啊
2. mainwindow.h文件
一些函数的声明和库的引用
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QSerialPort>
#include <QTimer>
#include <QWebEngineView>
QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_openMyCom1_clicked(); // 打开串口按钮的槽函数
void on_closeMyCom1_clicked(); // 关闭串口按钮的槽函数
void readMyCom1(); // 读取串口数据的槽函数
private:
void Com1Init(); // 初始化串口
void sendInitializationCommand(); // 发送初始化指令
void clear_gps(); // 清除GPS显示数据
void parseGPSData(const QString &data); // 解析GPS数据
void processBuffer(); // 处理缓冲区数据
Ui::MainWindow *ui;
QSerialPort *myCom1; // 串口对象
bool isComOpen1; // 串口是否打开的标志
QTimer *readTimer; // 定时器对象
QWebEngineView *mapView; // 地图视图对象
QString buffer; // 缓冲区用于处理串口数据
};
#endif // MAINWINDOW_H
3. mainwindow.cpp文件
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
#include <QSerialPort>
#include <QStringList>
#include <QTimer>
#include <QWebEngineView>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QNetworkProxyFactory>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
, myCom1(nullptr)
, isComOpen1(false)
{
ui->setupUi(this);
// 关闭系统代理配置
QNetworkProxyFactory::setUseSystemConfiguration(false);
// 创建一个定时器,用于定期读取串口数据
readTimer = new QTimer(this);
connect(readTimer, &QTimer::timeout, this, &MainWindow::readMyCom1);
// 设置UI布局
QWidget *centralWidget = new QWidget(this);
setCentralWidget(centralWidget);
QHBoxLayout *mainLayout = new QHBoxLayout(centralWidget);
// 创建地图显示区域
mapView = new QWebEngineView(this);
QString mapHtml = R"(
<div id="container" style="width:100%;height:100%;"></div>
<div class="input-card">
<h4>左击获取经纬度:</h4>
<div class="input-item">
<input type="text" readonly="true" id="lnglat">
</div>
</div>
<script type="text/javascript">
window._AMapSecurityConfig = {
securityJsCode: "c9e3585d7ef259a3b71826aafccafeec",
};
</script>
<script
type="text/javascript"
src="https://webapi.amap.com/maps?v=2.0&key=dca75fbd0390e9266a7f39814e1197c2"
></script>
<script type="text/javascript">
var map = new AMap.Map('container', {
rotateEnable: true,
resizeEnable: true,
pitchEnable: true,
zoom: 17,
pitch: 50,
rotation: -15,
viewMode: '3D',
zooms: [2, 20],
center: [104.10113, 30.675639],
layers: [
new AMap.TileLayer()
]
});
// 添加 3D 罗盘控制
AMap.plugin(['AMap.ControlBar'], function() {
map.addControl(new AMap.ControlBar());
});
// 添加点击事件获取经纬度
map.on('click', function(e) {
document.getElementById("lnglat").value = e.lnglat.getLng() + ',' + e.lnglat.getLat();
});
// 添加卫星图层
var satelliteLayer = new AMap.TileLayer.Satellite();
satelliteLayer.setMap(map);
// 添加实时路况图层
var trafficLayer = new AMap.TileLayer.Traffic({zIndex: 10});
trafficLayer.setMap(map);
// 添加楼块图层
var buildingsLayer = new AMap.Buildings({
zooms: [16, 18],
zIndex: 10,
heightFactor: 2
});
map.add(buildingsLayer);
// 添加卫星路网图层
map.addLayer(new AMap.TileLayer.Satellite());
map.addLayer(new AMap.TileLayer.RoadNet());
// 添加楼块图层
map.setMapStyle('amap://styles/light');
map.addLayer(buildingsLayer);
var marker = null;
var infoWindow = null;
function updateMap(lat, lon) {
var position = new AMap.LngLat(lon, lat);
map.setCenter(position);
map.setZoom(17);
if (marker) {
marker.setPosition(position);
} else {
marker = new AMap.Marker({
position: position,
map: map,
icon: new AMap.Icon({
image: 'http://webapi.amap.com/theme/v1.3/markers/n/mark_b.png', // 使用红色标记图标
size: new AMap.Size(24, 36) // 设置图标大小
})
});
}
if (infoWindow) {
infoWindow.setPosition(position);
} else {
infoWindow = new AMap.InfoWindow({
content: "<div style='color: red;'>塔吊1</div>", // 红色文字
position: position
});
infoWindow.open(map, marker.getPosition());
}
}
</script>
)";
mapView->setHtml(mapHtml);
mainLayout->addWidget(mapView, 1);
// 创建右侧控制区域
QVBoxLayout *controlLayout = new QVBoxLayout();
// 添加打开串口按钮
QPushButton *openButton = new QPushButton("打开串口", this);
connect(openButton, &QPushButton::clicked, this, &MainWindow::on_openMyCom1_clicked);
controlLayout->addWidget(openButton);
// 添加关闭串口按钮
QPushButton *closeButton = new QPushButton("关闭串口", this);
closeButton->setEnabled(false); // 默认关闭串口按钮不可用
connect(closeButton, &QPushButton::clicked, this, &MainWindow::on_closeMyCom1_clicked);
controlLayout->addWidget(closeButton);
// 添加经纬度显示标签
ui->latitudeLabel = new QLabel("纬度: ", this);
ui->longitudeLabel = new QLabel("经度: ", this);
controlLayout->addWidget(ui->latitudeLabel);
controlLayout->addWidget(ui->longitudeLabel);
mainLayout->addLayout(controlLayout);
ui->openMyCom1 = openButton;
ui->closeMyCom1 = closeButton;
}
MainWindow::~MainWindow()
{
if (myCom1) {
if (myCom1->isOpen()) {
myCom1->close();
}
delete myCom1;
}
delete ui;
}
// 打开串口并读取北斗数据
void MainWindow::on_openMyCom1_clicked()
{
ui->openMyCom1->setEnabled(false);
ui->closeMyCom1->setEnabled(true);
Com1Init(); // 初始化串口
if (isComOpen1) {
sendInitializationCommand(); // 发送初始化指令
readTimer->start(1000); // 启动定时器,每秒读取一次串口数据
} else {
ui->latitudeLabel->setText("串口打开失败:" + myCom1->errorString());
}
}
// 关闭串口
void MainWindow::on_closeMyCom1_clicked()
{
if (myCom1) {
if (myCom1->isOpen()) {
myCom1->close();
}
delete myCom1;
myCom1 = nullptr;
isComOpen1 = false;
readTimer->stop();
clear_gps();
ui->openMyCom1->setEnabled(true);
ui->closeMyCom1->setEnabled(false);
}
}
// 初始化串口
void MainWindow::Com1Init()
{
if (myCom1) {
delete myCom1;
}
QString portName1 = "COM12"; // 获取串口名
myCom1 = new QSerialPort(portName1);
if (!myCom1->open(QIODevice::ReadWrite)) {
qDebug() << "串口打开失败:" << myCom1->errorString();
ui->latitudeLabel->setText("串口打开失败:" + myCom1->errorString());
delete myCom1;
myCom1 = nullptr;
isComOpen1 = false;
return;
}
isComOpen1 = true;
myCom1->setBaudRate(QSerialPort::Baud9600);
myCom1->setDataBits(QSerialPort::Data8);
myCom1->setParity(QSerialPort::NoParity);
myCom1->setStopBits(QSerialPort::OneStop);
myCom1->setFlowControl(QSerialPort::NoFlowControl);
qDebug() << "串口打开成功";
}
// 发送初始化指令
void MainWindow::sendInitializationCommand()
{
if (myCom1 && myCom1->isOpen()) {
QString command = "$PCAS03,0,0,0,0,1,0,0,0,0,0,,,0,0*03\r\n";
myCom1->write(command.toUtf8());
}
}
// 读取串口数据
void MainWindow::readMyCom1()
{
if (myCom1 && myCom1->isOpen()) {
QByteArray temp1 = myCom1->readAll();
if (!temp1.isEmpty()) {
QString dataStr = QString::fromStdString(temp1.toStdString());
buffer += dataStr;
processBuffer();
}
} else {
qDebug() << "尝试读取数据时,串口未打开";
}
}
// 处理缓冲区中的数据
void MainWindow::processBuffer()
{
int startIndex = buffer.indexOf("$GNRMC");
if (startIndex == -1) {
return;
}
int endIndex = buffer.indexOf("\r\n", startIndex);
if (endIndex == -1) {
return;
}
QString gpsData = buffer.mid(startIndex, endIndex - startIndex);
buffer.remove(0, endIndex + 2);
parseGPSData(gpsData);
}
// 解析GPS数据并显示经纬度
void MainWindow::parseGPSData(const QString &data)
{
if (data.startsWith("$GNRMC")) {
QStringList dataList = data.split(",");
if (dataList.size() > 6) {
QString latitude = dataList[3];
QString latitudeDirection = dataList[4];
QString longitude = dataList[5];
QString longitudeDirection = dataList[6];
double lat = latitude.mid(0, 2).toDouble() + (latitude.mid(2).toDouble() / 60.0);
if (latitudeDirection == "S") lat = -lat;
double lon = longitude.mid(0, 3).toDouble() + (longitude.mid(3).toDouble() / 60.0);
if (longitudeDirection == "W") lon = -lon;
qDebug() << "纬度: " << lat << ", 经度: " << lon;
ui->latitudeLabel->setText("纬度: " + QString::number(lat, 'f', 6));
ui->longitudeLabel->setText("经度: " + QString::number(lon, 'f', 6));
QString jsCode = QString("updateMap(%1, %2);").arg(lat).arg(lon);
mapView->page()->runJavaScript(jsCode);
}
}
}
// 清除GPS显示数据
void MainWindow::clear_gps()
{
ui->latitudeLabel->clear();
ui->longitudeLabel->clear();
}
(1)高德地图的api调用
然后结合官方文档可以实现api的调用https://lbs.amap.com/api/javascript-api-v2/guide/abc/jscode
(2)高德地图的花花设置
本例程主要参考了https://gitee.com/alon-1787/qt_-gao-de_-map_1
采用了3D视图并增加了3D罗盘和地图标注的功能,一些其他的功能读者可以自行查阅,网上应该很多例子。
(3)GPS数据的解析
本示例采用的L76K模块,根据相关手册可知如果不设置串口,则接收到所有的信息
但是本例程只需要经纬度数据,所以先给串口发送 $PCAS03,0,0,0,0,1,0,0,0,0,0,,,0,0*03指令让其只打印出经纬度信息
通过观察输出的数据格式我们进行解析,具体的解析方法如下
// 解析GPS数据并显示经纬度
void MainWindow::parseGPSData(const QString &data)
{
if (data.startsWith("$GNRMC")) {
QStringList dataList = data.split(",");
if (dataList.size() > 6) {
QString latitude = dataList[3];
QString latitudeDirection = dataList[4];
QString longitude = dataList[5];
QString longitudeDirection = dataList[6];
double lat = latitude.mid(0, 2).toDouble() + (latitude.mid(2).toDouble() / 60.0);
if (latitudeDirection == "S") lat = -lat;
double lon = longitude.mid(0, 3).toDouble() + (longitude.mid(3).toDouble() / 60.0);
if (longitudeDirection == "W") lon = -lon;
qDebug() << "纬度: " << lat << ", 经度: " << lon;
ui->latitudeLabel->setText("纬度: " + QString::number(lat, 'f', 6));
ui->longitudeLabel->setText("经度: " + QString::number(lon, 'f', 6));
QString jsCode = QString("updateMap(%1, %2);").arg(lat).arg(lon);
mapView->page()->runJavaScript(jsCode);
}
}
}
(4)一个小tip
对了捏有一个小经验想分享给大家,刚开始写完代码运行发现高德地图特别卡,就是不紧不慢到那种老一辈艺术家的淡定与从容,然后我开始以为是因为代码问题,发现代码没啥问题啊,我就查了一下。
刚开始有说关掉系统代理的https://blog.csdn.net/weixin_30598225/article/details/97578556
QNetworkProxyFactory::setUseSystemConfiguration(false);
但是我试了不行唉,然后接着查,终于找到了一篇救我狗命的文章https://blog.csdn.net/weixin_43196399/article/details/129089971
只需要改一下左下角的debug版本成release版本,直接答辩通畅了
4. mainwindow.ui文件
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QWebEngineView" name="mapView" native="true">
<property name="minimumSize">
<size>
<width>400</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QPushButton" name="openMyCom1">
<property name="text">
<string>打开串口</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="closeMyCom1">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>关闭串口</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="latitudeLabel">
<property name="text">
<string>纬度: </string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="longitudeLabel">
<property name="text">
<string>经度: </string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>25</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<customwidgets>
<customwidget>
<class>QWebEngineView</class>
<extends>QWidget</extends>
<header>qwebengineview.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
三. 写在最后
本例程其实只是笔者最近项目的一个子界面,最终的项目是要实现一个可视化大屏应用,后续项目完成之后的代码笔者也会陆续开源出来,供大家参考学习,也欢迎大家留言交流~
标签:ui,Qt,MainWindow,myCom1,QString,串口,new,高德,GPS From: https://blog.csdn.net/daviddou2022/article/details/140741906