Qt实时绘制飞行轨迹/移动轨迹实时显示/带旋转角度/平滑移动/效果一级棒/地面站软件开发/无人机管理平台

一、前言说明

近期将这个模块彻底优化了下,最开始做的轨迹移动,是每次收到一个坐标点,就移动过去,这样显得很尴尬,跳跃式的前进,比如两个点之间距离很大,明显能看到直接跳过去的,虽然结果一样,但是看起来总是很不舒服,于是后面换了一种方案,那就是地图厂家提供的轨迹类,比如百度地图是BMapLib.LuShu,天地图是T.CarTrack,高德地图是marker本身的move动画,用起来都挺方便,效果也都还可以,确实是平滑移动的,而且还可以控制速度,那么问题来了,都是只能提前设置好经纬度坐标数据集合,启动后才是根据计算好的路径来运动,这个用来做轨迹回放,效果一级棒,但是实时轨迹肯定不行了,因为实时轨迹是每次都添加一个点,并不能提前知道所有点的集合,那怎办?

总归是有办法的,其实无论是BMapLib.LuShu还是T.CarTrack,都是js代码里面实现的计算两个点之间距离,然后主动生成多个点,比如两个经纬度坐标之间,线性插值法生成了几十个经纬度坐标,这些点连起来和两个点连起来的直线是一样的效果,然后开启了定时器,每次移动一个点即可,这样相当于全部的路径都均等分了,所以移动起来效果就是平滑的,既然js有这样的算法,那c++肯定也是可以的,于是先AI了一下,果真非常简单,就几行代码即可,传入两个经纬度坐标,给定需要生成多少个点,然后返回生成好的点集合,马上验证,效果又是一级棒。而且由于不依赖地图厂家的js,只需要基础的绘制线条和移动标注点,所以很容易支持所有地图厂家,根本不需要引入额外的js库。

二、效果图




三、相关代码

cpp 复制代码
#include "frmmapgpslive.h"
#include "ui_frmmapgpslive.h"
#include "qthelper.h"
#include "webview.h"
#include "maphelper.h"
#include "maputil.h"

frmMapGpsLive::frmMapGpsLive(QWidget *parent) : QWidget(parent), ui(new Ui::frmMapGpsLive)
{
    ui->setupUi(this);
    this->initForm();
    this->initConfig();
    QMetaObject::invokeMethod(this, "loadMap", Qt::QueuedConnection);
}

frmMapGpsLive::~frmMapGpsLive()
{
    timer->stop();
    delete ui;
}

void frmMapGpsLive::initForm()
{
    //设置右侧固定宽度
    ui->frame->setFixedWidth(AppData::RightWidth - 50);

    //实例化浏览器控件并加入到布局
    mapObj = NULL;
    webView = new WebView(this);
    webView->setLayout(ui->gridLayout, "frmMapGpsLive");
    connect(webView, SIGNAL(loadSuccess()), this, SLOT(loadData()));

    //启动定时器模拟/每次取出一个数据作为轨迹发过去
    timer = new QTimer(this);
    connect(timer, SIGNAL(timeout()), this, SLOT(moveDevice()));
}

void frmMapGpsLive::initConfig()
{
    MapHelper::loadMapCore(ui->cboxMapCore, AppConfig::GpsLiveCore);
    connect(ui->cboxMapCore, SIGNAL(currentIndexChanged(int)), this, SLOT(saveConfig()));
    connect(ui->cboxMapCore, SIGNAL(currentIndexChanged(int)), this, SLOT(loadMap()));

    ui->cboxMapLocal->setCurrentIndex(AppConfig::GpsLiveLocal ? 1 : 0);
    connect(ui->cboxMapLocal, SIGNAL(currentIndexChanged(int)), this, SLOT(saveConfig()));
    connect(ui->cboxMapLocal, SIGNAL(currentIndexChanged(int)), this, SLOT(loadMap()));

    ui->cboxMapType->setCurrentIndex(AppConfig::GpsLiveType);
    connect(ui->cboxMapType, SIGNAL(currentIndexChanged(int)), this, SLOT(saveConfig()));
    connect(ui->cboxMapType, SIGNAL(currentIndexChanged(int)), this, SLOT(loadMap()));

    ui->cboxSmooth->setCurrentIndex(AppConfig::GpsLiveSmooth ? 1 : 0);
    connect(ui->cboxSmooth, SIGNAL(currentIndexChanged(int)), this, SLOT(saveConfig()));
    connect(ui->cboxSmooth, SIGNAL(currentIndexChanged(int)), this, SLOT(loadMap()));
}

void frmMapGpsLive::saveConfig()
{
    AppConfig::GpsLiveCore = ui->cboxMapCore->itemData(ui->cboxMapCore->currentIndex()).toInt();
    AppConfig::GpsLiveLocal = (ui->cboxMapLocal->currentIndex() == 1);
    AppConfig::GpsLiveType = ui->cboxMapType->currentIndex();
    AppConfig::GpsLiveSmooth = (ui->cboxSmooth->currentIndex() == 1);
    AppConfig::writeConfig();
}

void frmMapGpsLive::loadMap()
{
    //根据不同地图内核实例化地图类
    MapCore mapCore = (MapCore)ui->cboxMapCore->itemData(ui->cboxMapCore->currentIndex()).toInt();
    int mapType = ui->cboxMapType->currentIndex();
    bool mapLocal = (ui->cboxMapLocal->currentIndex() == 1);
    int zoom = MapHelper::getMapZoom(mapCore, this->objectName());
    MapHelper::initMapObj(this, &mapObj, mapCore);
    mapObj->setWebView(webView);
    mapObj->setSaveFile(SaveFile);
    mapObj->setMapType(mapType);
    mapObj->setMapLocal(mapLocal);
    mapObj->setCenterPoint("121.428540,31.187120");
    mapObj->setZoom(zoom);
    mapObj->load();
}

void frmMapGpsLive::loadData()
{
    //先停止定时器
    timer->stop();

    srcIndexs.clear();
    srcPoints.clear();
    dstPoints.clear();

    markerFlags.clear();
    markerNames.clear();
    markerPoints.clear();
    lineFlags.clear();
    lineColors.clear();

    //由于不同地图厂家对应的坐标系不一样/加载不同的轨迹路径看起来会更合理/和对应的真实道路一致
    QString flag = MapHelper::getMapFlag(mapObj);
    QString file1 = QString(":/gps_%1_1.txt").arg(flag);
    QString file2 = QString(":/gps_%1_2.txt").arg(flag);

    //这里作为演示数据/实际情况可能通过网络通信拿到数据
    srcIndexs << -1 << -1;
    srcPoints << MapHelper::getGpsData(file1) << MapHelper::getGpsData(file2);
    dstPoints << QStringList() << QStringList();

    int count = srcPoints.count();
    QStringList points1 = srcPoints.at(0);
    QStringList points2 = srcPoints.at(1);
    if (points1.count() == 0 || points2.count() == 0) {
        return;
    }

    //默认取第一个点作为起始点
    markerFlags << "fly1" << "fly2";
    markerNames << "飞机1" << "飞机2";
    markerPoints << points1.first() << points2.first();
    lineFlags << "line1" << "line2";
    lineColors << "#ff0000" << "#222222";

    //第一步: 添加折线条
    for (int i = 0; i < count; ++i) {
        QString flag = lineFlags.at(i);
        QString point = markerPoints.at(i);
        QString color = lineColors.at(i);
        this->runJs(QString("addLine('%1', '%2', '%3', 5)").arg(flag).arg(point).arg(color));
    }

    //第二步: 添加标注点
    QString image = MapHelper::getFlyIcon(mapObj, false);
    for (int i = 0; i < count; ++i) {
        QString flag = markerFlags.at(i);
        QString name = markerNames.at(i);
        QString point = markerPoints.at(i);
        this->runJs(QString("addMarker('%1', '%2', '%3', '%4', 48, 48, true, -1, true)").arg(flag).arg(point).arg(name).arg(image));
    }

    //第三步: 添加折线点
    //this->runJs(QString("addDataByFlag('%1', '%2')").arg(flag).arg(point));
    //第四步: 移动标注点
    //this->runJs(QString("setMarker('%1', null, '%2', 60)").arg(flag).arg(point));

    //启动定时器插入数据
    timer->start(AppConfig::GpsLiveSmooth ? 10 : 100);
}

void frmMapGpsLive::moveDevice()
{
    int count = srcPoints.count();
    for (int i = 0; i < count; ++i) {
        if (AppConfig::GpsLiveSmooth) {
            QStringList points = dstPoints.at(i);
            if (points.count() > 0) {
                //取出一个数据执行移动
                QString point = points.takeFirst();
                dstPoints[i] = points;
                this->moveDevicex(i, point);
                continue;
            }
        }

        //移动到下一个数据
        srcIndexs[i]++;
        int index = srcIndexs.at(i);
        QStringList points = srcPoints.at(i);

        //到了末尾则复位折线
        if (index == points.count() - 1) {
            srcIndexs[i] = -1;
            markerPoints[i] = points.first();
            this->runJs(QString("resetPolyline('%1')").arg(lineFlags.at(i)));
            continue;
        }

        this->moveDevice(i, points.at(index));
    }
}

void frmMapGpsLive::moveDevice(int i, const QString &point)
{
    //取出两点之间的多个点/使得看起来平滑移动
    //按照两点之间的距离生成对应的点数/距离越大生成的点数越多
    if (AppConfig::GpsLiveSmooth) {
        QString point1 = markerPoints.at(i);
        double distance = MapUtil::getDistance(point1, point);
        if (distance > 0) {
            int count = distance / 2;
            QStringList points = MapUtil::getLinePoints(point1, point, count);
            points.removeLast();
            dstPoints[i] = points;
            return;
        }
    }

    //直接执行/看起来是跳跃的
    this->moveDevicex(i, point);
}

void frmMapGpsLive::moveDevicex(int i, const QString &point)
{
    //计算旋转角度/依赖上一个坐标
    int angle = MapUtil::getAngle(markerPoints.at(i), point);
    markerPoints[i] = point;

    //移动到新的位置
    this->runJs(QString("addDataByFlag('%1', '%2')").arg(lineFlags.at(i)).arg(point));
    this->runJs(QString("setMarker('%1', null, '%2', %3)").arg(markerFlags.at(i)).arg(point).arg(angle));
}

void frmMapGpsLive::runJs(const QString &js)
{
    if (mapObj) {
        mapObj->runJs(js);
    }
}

四、相关地址

  1. 国内站点:https://gitee.com/feiyangqingyun
  2. 国际站点:https://github.com/feiyangqingyun
  3. 个人作品:https://blog.csdn.net/feiyangqingyun/article/details/97565652
  4. 文件地址:https://pan.baidu.com/s/1ZxG-oyUKe286LPMPxOrO2A 提取码:o05q 文件名:bin_map.zip

五、功能特点

5.1 地图功能

  1. 支持多种地图内核,默认采用百度地图,可选高德地图、天地图、腾讯地图、谷歌地图等。
  2. 同时支持在线地图和离线地图两种模式,离线地图方便在不联网的场景中使用。
  3. 支持各种地图控件的启用,比如地图导航、地图类型、缩略图、比例尺、全景导航、实时路况、绘图工具、结果面板等。
  4. 支持多种地图功能的动态启用禁用,比如地图拖曳、键盘操作、滚轮缩放、双击放大、连续缩放、地图测距等。
  5. 提供众多js函数接口用于交互,参数极其丰富,能够想到的应用场景需求都有。
  6. 统一的信号槽机制,地图中的结果统一信号发送出去,收到后根据type类型区分。
  7. 支持地图交互,比如鼠标按下获取对应位置的经纬度。单击标注点弹出对应点的信息。
  8. 支持添加标注、删除标注、移动标注、清空标注。
  9. 标注点可以指定图标图片和尺寸,支持gif动图,支持指定以图片中心对齐还是底部中心对齐。可以设置旋转角度,带富文本提示信息。
  10. 标注点事件支持单击发信号通知和自己弹框显示信息。
  11. 提供地址转坐标和坐标转地址接口。
  12. 支持各种图形绘制,包括折线图、多边形、矩形、圆形、弧线等。
  13. 可显示悬浮的绘图工具栏,直接在地图上划线、标注点、矩形、圆形等。
  14. 支持各种区域搜索,比如矩形区域、圆形区域,可以按照关键字匹配将搜索结果显示在地图中。
  15. 可动态添加离线的行政区边界点数据。可以搜索行政区划并获取该区域的边界点数据。数据可以保存到文件以便离线使用。
  16. 支持点聚合功能,多个小标注点合并到一个大标注点,防止点密集导致交互不友好。
  17. 可以添加海量点,每个点都可以单击获取对应坐标和信息。
  18. 所有的覆盖物信息比如标注点、矩形、多边形、折线图等,都可以主动获取对应的信息比如坐标点和路径等。
  19. 支持路径规划,支持公交路线、自驾路线、步行路线、骑行路线,不同查询支持不同策略,可选最少时间、最少换乘、不走高架等。
  20. 路径规划结果可以显示在地图中,也可以获取到路径点坐标集合。这个数据可以保存到文件,以便发给机器人或者无人机做导航用来轨迹移动。
  21. 可以设置不同的地图视图比如街道图、卫星图、混合图。
  22. 可以设置不同的样式,比如午夜蓝、青草绿等样式风格。
  23. 可以设置地图的旋转角度和倾斜角度。
  24. 提供经纬度坐标纠偏转换功能,比如传入的GPS坐标需要转换到百度地图坐标或者高德地图坐标。各种坐标系转换全部离线函数,支持地球坐标系WGS-84、火星坐标系GCJ-02、百度坐标系BD-09之间的互相转换,涵盖了各种地图的坐标系。
  25. 提供动态轨迹点移动功能,按照给定的经纬度坐标集合平滑移动。
  26. 同时支持qwidget和qml,支持编译到安卓系统运行。

5.2 其他功能

  1. 提供离线地图下载模块,可以选择不同的地图内核比如百度地图或者谷歌地图,不同的地图类型比如下载街道图还是卫星图,不同的地图层级,多线程极速下载。
  2. 表格行实时显示对应的瓦片下载进度,有下载超时时间,重试次数,每个瓦片下载完成都发送信号通知,参数包括下载用时。
  3. 提供省市轮廓图下载模块,自动下载各个地区的轮廓图,保存到脚本文件或者文本文件。
  4. 支持手动调整不同区域的轮廓边界,调整后可以主动获取调整后的边界点集合。
  5. 提供动态点位示例,手动在地图上选点并添加标注,附带自定义的信息比如速度和时间等。
  6. 提供海量点位示例,批量添加标注点、点聚合、海量点。用于测试环境中支持的最大点位性能。
  7. 提供动态轨迹示例,在地图上鼠标按下选择起点和终点后,查询路线,获取路径轨迹点,模拟轨迹平滑移动。可以筛选数据将过多的路径点筛选到设定的点数。
  8. 提供轨迹回放示例,按照指定的轨迹点列表回放,也可以导入轨迹点数据进行回放。同时支持在街道图、卫星图、混合图中回放轨迹。
  9. 提供省市区域地图示例,采用echart组件,同时支持闪烁点图、迁徙图、区域地图、世界地图、仪表盘等。可以设置标题、提示信息、背景颜色、文字颜色、线条颜色、区域颜色等各种颜色。
  10. 省市区域地图示例,内置世界地图、全国地图、省份地图、地区地图,可以精确到县,所有地图全部离线使用。可设置城市的名称、值、经纬度集合。
  11. 内置通用浏览器组件,同时支持webkit/webengine/miniblink等内核。提供网页控件示例,演示打开网页和本地网页文件。
  12. 支持任意Qt版本、任意系统、任意编译器。
相关推荐
用户805533698033 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner3 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz8 天前
QML Hello World 入门示例
qt
xcyxiner11 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner12 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner12 天前
DicomViewer (添加模型类)3
qt
xcyxiner13 天前
DicomViewer (目录调整) 2
qt
xcyxiner13 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
24年生活经验_小旻14 天前
ROS2+Ardupilot+Gazebo通信仿真基本环境搭建
无人机
嘉子的秃头日记15 天前
TRO 2026|无人机能不能像积木一样,拼出不同形态?
无人机