Qt5 互动地图,实现无人机地面站效果

一、概述

本文主要通过Qt5+opmapcontrol实现一个简单的无人机地面站效果。opmapcontrol是一个比较古老的QT开源地面站库,可选择谷歌地图,必应地图, 雅虎地图,GIS等。可直接使用源码,也可以编译生成库进行调用。
实现效果:

二、环境

Qt:5.15.2

编译器: Qt 5.15.2 MinGW 64-bit

系统:windows 10

三. 功能特点

(1) 支持缓存地图

(2)支持选择各厂商地图,以及切换街道地图

(3)支持地图互动:拖动、放大缩小

(4)支持添加航点,以及航点的编辑、删除、保存、加载、航点信息显示

(5)支持设置home,以及安全区域

(6)支持显示运动轨迹

四.opmapcontrol

从github上下载下来的源码包调用的时候,不同版本的Qt会有些小bug,可以直接搜opmapcontrol。
CSDN免积分下载链接 (包含编译好的库、和简单的调用demo):
https://download.csdn.net/download/ever__ever/89290882?spm=1001.2014.3001.5503
(以下为opmapcontrol中mapwidget子项目各个类的功能讲解,只想知道如何使用的话可以跳过本节,直接前往第五节)
这个库中我们主要看mapwidget子项目,里面提供了对地图、无人机、航点、轨迹、home等相关操作的接口。

  1. OPMapWidget类

这个类主要继承QGraphicsView,提供一个地图交互场景视图。
(1)void SetShowDiagnostics(bool const & value)

设置是否显示诊断信息。当传入的值为true 时,会创建定时器用于刷新诊断信息,同时创建GPS对象(如果不存在),并设置透明度。当传入的值为false 时,会清除诊断信息、定时器、GPS对象等。
(2)void SetUavPic(QString UAVPic)

用于设置无人机的图片。如果无人机对象存在,会调用其SetUavPic函数设置图片。
(3)UAVItem* AddUAV(int id)

用于添加一个新的无人机对象,并将其添加到地图中。它返回指向新添加的无人机对象的指针。
(4)void AddUAV(int id, UAVItem* uav)

用于添加一个已存在的无人机对象,并将其添加到地图中。
(5)void DeleteUAV(int id)

用于删除指定ID的无人机对象。
(6)UAVItem* GetUAV(int id)

用于获取指定ID的无人机对象。
(7)const QList<UAVItem*> GetUAVS()

用于获取所有无人机对象的列表。
(8)WayPointLine *WPLineCreate(WayPointItem *from, WayPointItem *to, QColor color, bool dashed, int width)

用于创建两个航点之间的连线。
(9)WayPointLine *WPLineCreate(HomeItem *from, WayPointItem *to, QColor color, bool dashed, int width)

用于创建Home点到航点之间的连线。

  1. Configuration类
    它主要用于集中管理地图控件的大部分配置选项。

这个类包含了一些成员变量,如EmptytileBrushEmptyTileTextEmptyTileBorders 等,用于配置地图空白瓦片的绘制、文本、边框等样式。同时,它还包含了一些成员函数,用于设置和获取地图的访问模式、语言、内存缓存使用情况、缓存文件的位置等。

总的来说,Configuration类的作用是提供一个集中管理地图控件配置选项的地方,使得在代码中可以方便地对这些配置进行设置和获取。

  1. GPSItem类

继承自QObjectQGraphicsItem 的类,用于在地图上表示一个无人机(UAV )。提供了一系列函数用于管理无人机的位置、轨迹以及到达状态等属性,以及相应的信号与槽机制用于与其他对象进行交互。

这个类的作用主要有以下几点:

  • **表示无人机:**GPSItem类用于在地图上绘制表示无人机的图形元素,可以根据给定的参数设置无人机的位置、高度和朝向。
  • **设置无人机轨迹:**可以根据不同的轨迹类型设置无人机的轨迹显示方式,包括按时间间隔绘制轨迹点或按距离间隔绘制轨迹点等。
  • **管理轨迹显示:**可以设置是否显示无人机的轨迹以及轨迹线,并提供相应的函数来控制轨迹的显示和清除。
  • **自动设置到达状态:**可以设置无人机自动根据距离到达航点,并根据设定的距离自动设置航点到达状态。
  1. HomeItem类

继承自QObjectQGraphicsItem 的类,用于在地图上表示一个home 点的位置。提供了一系列函数用于管理home的位置、安全区域以及相应的交互功能。

这个类的作用主要有以下几点:

  • **表示home的位置:**HomeItem类用于在地图上绘制表示home的图形元素,可以设置home的位置坐标和海拔高度。
  • **显示安全区域:**可以设置是否显示home的安全区域,并提供相应的函数进行控制。
  • **提供信号与槽机制:**通过信号与槽机制,可以实现当home的位置发生变化时通知其他对象,以及当home位置被双击时触发相应的事件。
  • **提供鼠标事件处理:**可以处理鼠标的移动、按下、释放和双击等事件,以便实现交互功能。
  1. MapGraphicItem类
    MapGraphicItem
    类是一个用于地图显示和交互的主要图形项。它是一个继承自 QObjectQGraphicsItem 的类,是地图小部件中核心的图形项,负责管理地图的显示和交互,以及地图相关的逻辑处理。

这个类的作用主要有以下几点:

  • **显示地图:**负责在地图小部件上绘制地图,并处理地图的显示逻辑。
  • **地图交互:**处理鼠标事件(移动、点击、滚动等)和键盘事件,以实现地图的交互功能,如拖拽、缩放、选择区域等。
  • **地图坐标转换:**提供方法将经纬度坐标转换为本地坐标,以及将本地坐标转换为经纬度坐标。
  • **地图操作:**包括设置地图的缩放级别、旋转角度、地图类型等。
  • **地图重绘:**在需要更新地图显示时,负责重新绘制地图。
  • **地图逻辑:**处理地图的逻辑操作,如获取地图类型、设置当前位置、设置鼠标滚轮缩放类型等。
  1. TrailItem类

用于在地图上显示路径轨迹的图形项,它可以根据传入的经纬度坐标和颜色刷,在地图上绘制路径。

  1. UAVItem类

表示无人机(UAV)的图形项,用于在地图上显示无人机的位置、航向、速度等信息。可以根据无人机的位置和状态信息进行绘制,同时提供了一系列方法和信号,用于处理与无人机相关的逻辑。

这个类的作用主要有以下几点:

  • **初始化无人机项:**通过构造函数初始化无人机项,传入地图图形项、父部件指针和无人机图片路径等参数。
  • **设置无人机信息:**提供一系列方法,用于设置无人机的位置、NED(北东地)坐标、地面速度、空速、航向角速度等信息。
  • **绘制无人机:**实现 paint 方法,用于在地图上绘制无人机。绘制时可以根据当前无人机的位置、航向等信息进行绘制。
  • **边界矩形:**实现 boundingRect 方法,返回无人机项的边界矩形,用于在场景中定位无人机项的位置。
  • **类型标识:**定义了 type 方法,返回无人机项的类型标识,方便识别无人机项。
  • **信号与槽:**提供多个槽函数,用于处理无人机位置更新、透明度设置等操作。还定义了一些信号,用于与其他部件进行通信,如当无人机达到航点时发出信号等。
  1. WayPointItem类

表示航点的图形项,用于在地图上显示航点的位置、描述和相关信息。可以根据航点的位置和状态信息进行绘制,同时提供了一系列方法和信号,用于处理与航点相关的逻辑。

这个类的作用主要有以下几点:

  • **初始化航点项:**提供多个构造函数,用于初始化航点项,可以传入航点的经纬度坐标、海拔高度、描述信息等参数。
  • **设置航点信息:**提供一系列方法,用于设置和获取航点的描述、是否到达、航点编号、经纬度坐标、海拔高度等信息。
  • **绘制航点:**实现 paint 方法,用于在地图上绘制航点的图标,并根据航点是否到达以及是否显示航点编号进行绘制。
  • **边界矩形:**实现 boundingRect 方法,返回航点项的边界矩形,用于在场景中定位航点项的位置。
  • **类型标识:**定义了 type 方法,返回航点项的类型标识,方便识别航点项。

五.主程序结构

├── UAVMapDemo

├── UAVMapDemo.pro

├── main.cpp (程序入口:加载样式表)

├── mainwindow.h (主界面,没什么操作)

├── mainwindow.cpp

├── mainwindow.ui

├── 3rdparty (包括opmapcontrol的头文件和库文件)

│ ├── opmapcontrol

│ │ └── include

│ │ └── MinGW

├── MapControl

│ ├── MapControl.pri

│ ├── MapWidget.h (继承OPMapWidget,调用opmapwidget相关接口实现地图和无人机操作)

│ ├── MapWidget.cpp

│ ├── UAS_types.h (存放一些数据类型和转化)

│ ├── UAS_types.cpp

├── Robot

│ ├── Robot.pri

│ ├── uavmanagerwidget.h (无人机地面站界面,实现模型飞行、起飞、降落等接口)

│ ├── uavmanagerwidget.cpp

│ ├── uavmanagerwidget.ui

│ ├── mapsetmiddleform.h (一个小界面,不用关心)

│ ├── mapsetmiddleform.h

│ ├── mapsetmiddleform.h

├── res

│ ├── image (图片资源)

│ ├── styleSheet (样式资源)

│ │ └── default.qss

六.程序源码主要部分说明

  1. pro文件

添加opmapcontrol相关头文件和库文件

cpp 复制代码
CONFIG(debug, debug|release){
    win32-g++ {
    }
    else:msvc {
        }
}
else {
    win32-g++ {
        INCLUDEPATH += $$PWD/3rdparty/opmapcontrol/include/mapwidget

            LIBS += -L$$PWD/3rdparty/opmapcontrol/MinGW -lopmapwidget
            }
    else:msvc {
        }
}
  1. UAVManagerWidget 无人机地面站界面

initWidget () : 新建一个配置文件,存放经纬度、home点经纬度、缩放比例、添加一个无人机类,编号为0,不显示无人信息,设置无人机的位置为home位置。
handleUISignals () : 处理界面上的一些ui信号,加了一个定时器,用于模拟飞行。
flySimulation() : 模拟飞行,随机经纬度。

cpp 复制代码
void UAVManagerWidget::initWidget()
{
    m_mapSetForm = new MapSetMiddleForm();
    m_mapSetForm->setRobotBtnName(u8"无人机");

    m_conf = new QSettings("./MapControl/data/UAV.ini", QSettings::IniFormat);

    int zoom;
    m_homeLat = m_conf->value("mapWidget_home_lat", 34.257287).toDouble();
    m_homeLng = m_conf->value("mapWidget_home_lng", 108.888931).toDouble();
    m_uavLat = m_homeLat;
    m_uavLng = m_homeLng;
    zoom = m_conf->value("lastZoom", 13).toInt();

    internals::PointLatLng p(m_homeLat, m_homeLng);
    ui->widget_map->SetCurrentPosition(p);
    ui->widget_map->SetZoom(zoom);
    ui->widget_map->setConf(m_conf);

    uav = ui->widget_map->AddUAV(0);
    uav->SetNumber(0);
    uav->SetShowUAVInfo(false);
    uav->SetMapFollowType(mapcontrol::UAVMapFollowType::Types::CenterMap);
    uav->SetUAVPos(p, 10);
}

void UAVManagerWidget::handleUISignals()
{
    // 连接
    connect(ui->btn_connect, &QPushButton::clicked, this, &UAVManagerWidget::connectUav);
    // 起飞
    connect(ui->btn_takeoff, &QPushButton::clicked, this, &UAVManagerWidget::uavTakeoff);
    // 降落
    connect(ui->btn_land, &QPushButton::clicked, this, &UAVManagerWidget::uavLand);
    // 返回
    connect(ui->btn_back, &QPushButton::clicked, this, &UAVManagerWidget::uavGoHome);
    // plan
    connect(ui->btn_plan, &QPushButton::clicked, this, &UAVManagerWidget::flyByWaypoint);
    // 地图中心点
    connect(ui->btn_home, &QPushButton::clicked, this, [=]() {
        m_mapSetForm->setCurrentPos(ui->widget_map->CurrentPosition().Lng(),
            ui->widget_map->CurrentPosition().Lat());

        QPoint buttonPos = ui->btn_home->mapToGlobal(ui->btn_home->rect().center());
        int dialogX = buttonPos.x() + ui->btn_home->width() - 15;
        int dialogY = buttonPos.y() - m_mapSetForm->height() / 2;
        m_mapSetForm->showWidget(QPoint(dialogX, dialogY));
    });
    // 置于home
    connect(m_mapSetForm, &MapSetMiddleForm::signal_setHome, this, [=]() {
        ui->widget_map->SetCurrentPosition(internals::PointLatLng(m_homeLat, m_homeLng));
    });
    // 置于无人机
    connect(m_mapSetForm, &MapSetMiddleForm::signal_setRobot, this, [=]() {
        if (uav) {
            ui->widget_map->SetCurrentPosition(internals::PointLatLng(m_uavLat, m_uavLng));
        }
    });
    // 置于自定义中心
    connect(m_mapSetForm, &MapSetMiddleForm::signal_setCustom, this, [=](double lng, double lat) {
        if (uav) {
            ui->widget_map->SetCurrentPosition(internals::PointLatLng(lat, lng));
        }
    });
    // 无人机超出home设定安全范围
    connect(uav,
        SIGNAL(UAVLeftSafetyBouble(internals::PointLatLng)),
        this,
        SLOT(uavWarnning(internals::PointLatLng)));

    // 用于模拟飞行的定时器,随机生成无人机位置
    timer = new QTimer(this);
    connect(timer, SIGNAL(timeout()), this, SLOT(flySimulation()));
    // 模拟飞行按钮
    connect(ui->btn_simFly, &QPushButton::clicked, this, [=]() {
        if (ui->btn_simFly->isChecked()) {
            timer->start(100);
        } else {
            timer->stop();
        }
    });
}

void UAVManagerWidget::flySimulation()
{
    m_uavLat += 0.000001;
    m_uavLng += QRandomGenerator::global()->bounded(0.000001) - 0.000005;
    internals::PointLatLng ll(m_uavLat, m_uavLng);
    uav->SetUAVPos(ll, 10);
}
  1. MapWidget 地图窗口,继承OPMapwidget。

MapWidget() : 构造函数,选择必应地图,其他的需要科学上网。

cpp 复制代码
MapWidget::MapWidget(QWidget* parent)
    : mapcontrol::OPMapWidget(parent)
{
    m_conf = NULL;

    configuration->SetAccessMode(core::AccessMode::CacheOnly);
    configuration->SetTileMemorySize(200);
    configuration->SetCacheLocation("./data/");

    SetZoom(4);
    SetMinZoom(4);
    // 地图选择必应
    SetMapType(MapType::BingHybrid);
    // 显示罗盘
    SetShowCompass(true);
    // set initial values
    m_bSelectArea = 0;
    m_pSelectArea1.SetLat(-9999);
    m_pSelectArea1.SetLng(-9999);
    m_pSelectArea2.SetLat(-9999);
    m_pSelectArea2.SetLng(-9999);

    m_homeShow = 1;
    m_homeAlt = 440;
    m_homePos.SetLat(-9999);
    m_homePos.SetLng(-9999);
    m_homeSafearea = 100;

    m_flightHeight = 20;

    // setup menus
    setupMenu();
}

*setConf(QSettings conf) : **配置文件设置,地图加载接口(服务器、缓存)、地图类型、缓存位置、home点和安全区域设置。

cpp 复制代码
void MapWidget::setConf(QSettings* conf)
{
    m_conf = conf;

    if (m_conf == NULL)
        return;

    // map type & access mode
    {
        MapType::Types mapType;
        core::AccessMode::Types accessMode;
        QString cacheLocation;

        // load settings
        accessMode = (core::AccessMode::Types)m_conf->value("mapWidget_accessMode",
                                                        (int)(core::AccessMode::CacheOnly))
                         .toInt();
        mapType = (MapType::Types)m_conf->value("mapWidget_mapType",
                                            (int)(MapType::GoogleSatellite))
                      .toInt();
        cacheLocation = m_conf->value("mapWidget_cacheLocation", "./MapControl/data/").toString();

        // set configurations
        configuration->SetAccessMode(accessMode);
        configuration->SetCacheLocation(cacheLocation);
        SetMapType(mapType);

        // set accessMode actions
        if (accessMode == core::AccessMode::ServerAndCache) {
            m_actMapAccess_ServerAndCache->setChecked(true);
            m_actMapAccess_Cache->setChecked(false);
        } else if (accessMode == core::AccessMode::CacheOnly) {
            m_actMapAccess_ServerAndCache->setChecked(false);
            m_actMapAccess_Cache->setChecked(true);
        }
    }

    // home & safe area
    {
        m_homeShow = m_conf->value("mapWidget_home_show", m_homeShow).toInt();
        m_homePos.SetLat(m_conf->value("mapWidget_home_lat", m_homePos.Lat()).toDouble());
        m_homePos.SetLng(m_conf->value("mapWidget_home_lng", m_homePos.Lng()).toDouble());
        m_homeAlt = m_conf->value("mapWidget_home_alt", m_homeAlt).toDouble();
        m_homeSafearea = m_conf->value("mapWidget_home_safeArea", m_homeSafearea).toDouble();

        m_flightHeight = m_conf->value("mapWidget_flightHeight", m_flightHeight).toDouble();

        if (m_homeShow) {
            this->SetShowHome(true);
            this->Home->SetCoord(m_homePos);
            this->Home->SetAltitude((int)(m_homeAlt));
            this->Home->SetSafeArea((int)(m_homeSafearea));

            m_actHome_ShowHide->setChecked(true);
        } else {
            m_actHome_ShowHide->setChecked(false);
        }
    }
}

**setupMenu() **: 右键菜单,提供对地图、航点、home、安全区域等各个功能的接口。

cpp 复制代码
int MapWidget::setupMenu(void)
{
    // setup actions
    m_actMapType = new QAction(tr(u8"地图类型"), this);
    connect(m_actMapType, SIGNAL(triggered()), this, SLOT(actMapType_SelectMap()));

    m_actMapAccess_ServerAndCache = new QAction(tr(u8"服务器和缓存"), this);
    m_actMapAccess_ServerAndCache->setCheckable(true);
    m_actMapAccess_ServerAndCache->setChecked(false);
    connect(m_actMapAccess_ServerAndCache, SIGNAL(triggered()),
        this, SLOT(actMapAccess_ServerAndCache()));

    m_actMapAccess_Cache = new QAction(tr(u8"缓存"), this);
    m_actMapAccess_Cache->setCheckable(true);
    m_actMapAccess_Cache->setChecked(true);
    connect(m_actMapAccess_Cache, SIGNAL(triggered()), this, SLOT(actMapAccess_Cache()));

    m_actWaypoint_add = new QAction(tr(u8"航点添加"), this);
    connect(m_actWaypoint_add, SIGNAL(triggered()), this, SLOT(actWaypoint_add()));
    m_actWaypoint_del = new QAction(tr(u8"航点删除"), this);
    connect(m_actWaypoint_del, SIGNAL(triggered()), this, SLOT(actWaypoint_del()));
    m_actWaypoint_edit = new QAction(tr(u8"航点编辑"), this);
    connect(m_actWaypoint_edit, SIGNAL(triggered()), this, SLOT(actWaypoint_edit()));
    m_actWaypoint_clear = new QAction(tr(u8"航点清除"), this);
    connect(m_actWaypoint_clear, SIGNAL(triggered()), this, SLOT(actWaypoint_clear()));
    m_actWaypoint_save = new QAction(tr(u8"航点保存"), this);
    connect(m_actWaypoint_save, SIGNAL(triggered()), this, SLOT(actWaypoint_save()));
    m_actWaypoint_load = new QAction(tr(u8"航点加载"), this);
    connect(m_actWaypoint_load, SIGNAL(triggered()), this, SLOT(actWaypoint_load()));

    m_actSelectArea_beg = new QAction(tr(u8"选择区域起点"), this);
    connect(m_actSelectArea_beg, SIGNAL(triggered()), this, SLOT(actSelectArea_beg()));
    m_actSelectArea_end = new QAction(tr(u8"选择区域终点"), this);
    connect(m_actSelectArea_end, SIGNAL(triggered()), this, SLOT(actSelectArea_end()));
    m_actSelectArea_clear = new QAction(tr(u8"选择区域清除"), this);
    connect(m_actSelectArea_clear, SIGNAL(triggered()), this, SLOT(actSelectArea_clear()));

    m_actHome_Set = new QAction(tr(u8"设置Home"), this);
    connect(m_actHome_Set, SIGNAL(triggered()), this, SLOT(actHome_Set()));
    m_actHome_Safearea = new QAction(tr(u8"设置Home安全区域"), this);
    connect(m_actHome_Safearea, SIGNAL(triggered()), this, SLOT(actHome_Safearea()));
    m_actHome_ShowHide = new QAction(tr(u8"显示/隐藏Home"), this);
    m_actHome_ShowHide->setCheckable(true);
    m_actHome_ShowHide->setChecked(true);
    connect(m_actHome_ShowHide, SIGNAL(triggered()), this, SLOT(actHome_ShowHide()));

    m_actCacheMap = new QAction(tr(u8"缓存地图"), this);
    connect(m_actCacheMap, SIGNAL(triggered()), this, SLOT(actCacheMap()));

    m_actTrail_clear = new QAction(tr(u8"清除轨迹"), this);
    connect(m_actTrail_clear, SIGNAL(triggered()), this, SLOT(actClearTrail()));

    // setup menu
    m_popupMenu = new QMenu("Menu");

    QMenu* menuAccessMode = m_popupMenu->addMenu(u8"接口类型");
    m_popupMenu->addAction(m_actMapType);
    m_popupMenu->addAction(m_actCacheMap);
    m_popupMenu->addSeparator();
    m_popupMenu->addAction(m_actTrail_clear);
    m_popupMenu->addSeparator();
    m_popupMenu->addAction(m_actWaypoint_add);
    m_popupMenu->addAction(m_actWaypoint_del);
    m_popupMenu->addAction(m_actWaypoint_edit);
    m_popupMenu->addAction(m_actWaypoint_clear);
    m_popupMenu->addAction(m_actWaypoint_save);
    m_popupMenu->addAction(m_actWaypoint_load);
    m_popupMenu->addSeparator();
    m_popupMenu->addAction(m_actSelectArea_beg);
    m_popupMenu->addAction(m_actSelectArea_end);
    m_popupMenu->addAction(m_actSelectArea_clear);
    m_popupMenu->addSeparator();
    m_popupMenu->addAction(m_actHome_Set);
    m_popupMenu->addAction(m_actHome_Safearea);
    m_popupMenu->addAction(m_actHome_ShowHide);

    menuAccessMode->addAction(m_actMapAccess_ServerAndCache);
    menuAccessMode->addAction(m_actMapAccess_Cache);

    return 0;
}

七.最终效果

八.源代码链接

https://download.csdn.net/download/ever__ever/89291117?spm=1001.2014.3001.5503

相关推荐
创小董32 分钟前
高海拔低温地区无人机大载重吊运技术详解
无人机
云空2 小时前
《QT 5.14.1 搭建 opencv 环境全攻略》
开发语言·qt·opencv
小老鼠不吃猫3 小时前
力学笃行(二)Qt 示例程序运行
开发语言·qt
晓纪同学4 小时前
QT创建一个模板槽和信号刷新UI
开发语言·qt·ui
爱码小白6 小时前
PyQt5 学习方法之悟道
开发语言·qt·学习方法
创小董7 小时前
垂起固定翼无人机大面积森林草原巡检技术详解
无人机
IT猿手13 小时前
基于PWLCM混沌映射的麋鹿群优化算法(Elk herd optimizer,EHO)的多无人机协同路径规划,MATLAB代码
算法·elk·机器学习·matlab·无人机·聚类·强化学习
创小董13 小时前
无人机飞防高效率喷洒技术详解
无人机
人才程序员18 小时前
QML z轴(z-order)前后层级
c语言·前端·c++·qt·软件工程·用户界面·界面
学习BigData19 小时前
【使用PyQt5和YOLOv11开发电脑屏幕区域的实时分类GUI】——选择检测区域
qt·yolo·分类