无人机编队飞行模拟/无人机集群仿真/可用于各种无人机应用二开/完整源码

一、前言说明

之前就思考过这些功能,受限于之前web版本的地图控件,交互体验总体上不是很满意,加上性能这块,一直迟迟没有大规模推进,现在纯qwidget版本的地图控件完成以后,底层基座全部做好以后,可以快速开展了,专门搞了个子目录mapexpand存放相关的类。以前web的搞个几百个飞行模拟,直接卡成屎,不管是百度地图还是高德地图还是啥牛逼地图,都是卡成屎,现在有了CS版本的地图控件,性能暴增百倍不止,上万个都毫无压力,而且拓展性和灵活性大大增强,要绘制各种各样的效果,都是分分钟的事情,之前还要先用js实现,再暴露对应的接口,再qt这边调用接口交互,而且每一种厂家的地图都要去实现对应的功能,现在根本不用管瓦片源头是谁,反正底层基座都是好的,只是切换一个地图源的问题,而且图形图层和瓦片图层分离,完全不相关不影响,任意切换各种地图源。

二、效果图

Snipaste_2026-09-17_15-39-55

Snipaste_2026-09-17_15-40-18

三、相关代码

#include "simulatefly.h"
#include "simulatehelper.h"
#include "maputil.h"
#include "markermove.h"
#include "overlaymarker.h"

SimulateFly::SimulateFly(MapWidget *mapWidget, int number, int speed, int interval) : QObject(mapWidget)
{
    this->mapWidget = mapWidget;
    connect(mapWidget, SIGNAL(mapEvent(int, bool, QPointF)), this, SLOT(mapEvent(int, bool, QPointF)));

    this->number = number;
    this->speed = speed;
    this->interval = interval;

    //定时器生成飞行坐标
    timer = new QTimer(this);
    connect(timer, SIGNAL(timeout()), this, SLOT(generate()));
    timer->setInterval(interval);
}

SimulateFly::~SimulateFly()
{
    this->clear();
}

void SimulateFly::start()
{
    //先清空再创建仿真对象
    this->clear();
    this->init();

    //启动定时器
    timer->start();
}

void SimulateFly::stop()
{
    timer->stop();
}

void SimulateFly::clear()
{
    //停止定时器
    this->stop();

    //清空所有飞机
    foreach (MarkerMove *fly, flys) {
        fly->clear();
        fly->deleteLater();
    }

    flys.clear();
}

void SimulateFly::init()
{
    //以地图中心作为起点
    flyAngle = -1;
    centerPoint = mapWidget->getCenter();

    //根据数量创建飞行器
    if (number <= 10) {
        //只创建一个飞行器
        createFly("fly0", centerPoint);
    } else {
        //主飞机在最前面
        createFly("fly0", centerPoint);

        //获取像素偏移量
        int offset = 50;
        int zoom = mapWidget->getZoom();
        qreal dpp = MapUtil::getDegreesPerPixel(zoom, centerPoint.y());
        qreal dlng = offset * dpp;

        //中间跟随飞机
        QPointF follow1(centerPoint.x() - dlng, centerPoint.y());
        createFly("fly1", follow1);

        //左下方跟随飞机
        QPointF follow2(centerPoint.x() - dlng, centerPoint.y() + dlng * 0.866);
        createFly("fly2", follow2);

        //右下方跟随飞机
        QPointF follow3(centerPoint.x() - dlng, centerPoint.y() - dlng * 0.866);
        createFly("fly3", follow3);
    }
}

void SimulateFly::createFly(const QString &flag, const QPointF &coord)
{
    //创建飞行对象
    bool first = (flag == "fly0");
    bool autoCenter = first;
    QPixmap pixmap = first ? SimulateHelper::flyRed() : SimulateHelper::flyBlue();
    MarkerMove *fly = new MarkerMove(mapWidget, flag, pixmap, QVector<QPointF>() << coord, speed, interval, false, false, autoCenter, false);

    //只需要显示移动轨迹
    fly->setDataLineEnable(false);
    fly->setDataLineVisible(false);
    fly->setMoveLineColor(first ? Qt::red : Qt::yellow, 3);

    //设置最大点数/防止内存一直涨
    fly->setMaxCount(1000);

    //旋转到默认往右朝向
    fly->setAngle(90);
    ((OverlayMarker *)fly->getMarker())->setRotation(90);

    flys << fly;
}

void SimulateFly::generate()
{
    //计算每像素对应的经纬度偏移量
    qreal factor = 10;
    int zoom = mapWidget->getZoom();
    qreal dpp = MapUtil::getDegreesPerPixel(zoom, centerPoint.y());
    qreal dlngBase = speed / factor * dpp;

    //为每个飞行器生成下一个坐标
    foreach (MarkerMove *fly, flys) {
        //根据飞行角度计算飞行方向
        qreal dlng, dlat;
        QPointF point = fly->getCoord();
        if (flyAngle > 0) {
            //使用用户指定的角度/需要将角度转换为标准的数学角度
            qreal radian = (90 - flyAngle) * M_PI / 180.0;
            dlng = dlngBase * cos(radian);
            dlat = dlngBase * sin(radian);
        } else {
            //默认往右侧飞行
            dlng = dlngBase;
            dlat = 0.0;
        }

        //在上一个点基础上加上偏移值
        point = QPointF(point.x() + dlng, point.y() + dlat);

        //如果超过地图范围则复位
        if (point.x() < -180.0 || point.x() > 180.0 || point.y() < -85.0 || point.y() > 85.0) {
            QMetaObject::invokeMethod(this, "reset", Qt::QueuedConnection);
            break;
        }

        //添加实时轨迹点/这里都禁用绘制
        fly->append(point, false);
        fly->pause();
        fly->move(false);
    }

    //最后统一在这里调用一次绘制/保证统一画面
    mapWidget->requestRedraw();
}

void SimulateFly::reset()
{
    this->flyAngle = -1;
    foreach (MarkerMove *fly, flys) {
        fly->reset(true, true);
        fly->setAngle(90);
    }
}

void SimulateFly::mapEvent(int type, bool right, const QPointF &point)
{
    //计算当前按下的坐标点和最后的点形成的角度
    if (type == MouseType_Press && !flys.isEmpty()) {
        flyAngle = MapUtil::getAngle(flys.first()->getCoord(), point);
        foreach (MarkerMove *fly, flys) {
            fly->setAngle(flyAngle > 0 ? flyAngle : -1);
        }
    }
}

四、相关地址

  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_mapwidget.zip

五、功能特点

  1. 支持各种地图源,包括天地图、高德地图、腾讯地图、谷歌地图、微软地图、电子海图、ArcGis、OpenStreet、360地图等。
  2. 同时支持WGS-84地球坐标系和GCJ-02火星坐标系,如果是天地图、谷歌地图,自动应用WGS-84坐标系,如果是高德地图、腾讯地图,自动应用GCJ-02坐标系。也可以主动设置在WGS-84坐标系加载高德地图,内部会自动处理坐标偏移。
  3. 同时支持默卡托投影和经纬度投影,比如天地图的地图源可以是默卡托投影或者经纬度投影。
  4. 每一种地图源都支持在线和离线两种场景需求,可以自定义在线瓦片地址格式和离线瓦片地址格式。
  5. 多线程下载和加载瓦片图片文件,多线程绘制,自动缓存瓦片文件。
  6. 在线模式下,可以开启是否缓存文件,指定缓存路径,将下载的瓦片文件存放到本地,默认优先从缓存文件查找,如果存在缓存文件则加载缓存文件,不存在则联网下载。
  7. 可以任意拖曳地图,鼠标滚轮放大和缩小地图,以鼠标所在位置作为缩放中心点,提供缩放控件手动单击进行操作,支持鼠标左键双击放大和右键双击缩小。
  8. 多图层机制,支持多个瓦片叠加图层和图形绘制图层,全部采用双缓冲技术,所有的图形和瓦片全部绘制到一个图片文件上,最终再将图片文件绘制到地图控件。不可见区域的图层包括覆盖物不会触发绘制,最大程度降低CPU占用。
  9. 动态图层机制,瓦片图层和图形图层分离,图形图层不更新的情况下,动态更新底图瓦片图层,任意切换更新多个瓦片图层。无需重新加载整个地图。
  10. 预加载机制,默认绘制的图层大小以当前区域往四周放大一倍,这样在鼠标拖动和缩放的时候,不会看到明显的加载过程,因为已经提前预加载好了,体验友好。
  11. 内置了多种图形覆盖物,包括坐标点、纯文本、标注点、折线条、多边形、矩形、圆形、雷达、点聚合、热力图等,可以设置边框颜色粗细、填充颜色和透明度等众多参数。
  12. 标注点支持旋转角度和提示文本,其中提示文本可以设置在标注点的相对位置,标注点图片支持gif动图,可以动态切换静态图和动图。
  13. 标注点和提示文本可以设置相对位置,位置包括左侧、右侧、上侧、下侧、中间、左上角、右上角、左下角、右下角。标注点默认按照底部居中对齐,一般圆形图标可以设置中心点对齐。
  14. 标注点提示文本可设置背景颜色,透明度、颜色边框和粗细,支持换行和多行文字。
  15. 所有的图形可以动态更新前景色、颜色粗细、背景颜色、颜色透明度等。
  16. 支持删除单个图形、删除一种类型的图形、删除所有图形、隐藏单个或者所有图形等。
  17. 支持动态绘制各种图形,开启后直接在地图上鼠标按下绘制,鼠标右键结束绘制,非常方便快捷。
  18. 在对应图形区域鼠标按下,发出图形单击信号,精准识别单击区域,比如折线以鼠标在折线条上作为判断依据,多边形区域以鼠标在整个多边形区域内为准,而不是以矩形区域,包括圆形也是以圆形内部为准。
  19. 可以动态启动禁用比例尺、十字线、缩放控件、地图拖曳、键盘操作、滚轮缩放、双击缩放、鼠标追踪等特性。
  20. 可以任意指定经纬度区域进行瓦片拼接保存成图片文件,也可以直接对整个可视区域或者缓存区域的地图图片文件保存。支持任意多边形轮廓保存成图片,比如某个行政区的瓦片保存。
  21. 图形可以动态设置zindex层叠顺序,值越大,越显示在前面,内部维护着一个zindex表,默认按照添加的先后顺序增加,后面添加的显示在前面,主动设置后,按照设置的zindex来绘制。
  22. 支持将QWidget对象作为覆盖物添加到地图控件中,跟随地图移动位置,极大提高灵活性,比如可以将自定义控件直接作为地图控件的子对象加入进去。
  23. 支持测距和测面积,动态绘制多边形区域,实时动态调整位置和点坐标自动重新计算,可以关闭单个测距测面的图形。
  24. 内置MarkerMove轨迹移动类,支持历史轨迹数据回放和实时轨迹移动,可设置图标、轨迹线的颜色和粗细、移动速度、移动间隔、平滑移动、自动中心等,支持多条轨迹线条同时移动。
  25. 内置MarkerLine航迹规划类,支持动态添加航迹点,显示对应箭头,可以动态拖曳调整航迹点的位置,选中点高亮显示。
  26. 大量使用按需绘制机制,包括内部提供合理的默认值来触发绘制,也可以手动传入参数指定是否需要立即绘制,比如删除了某个覆盖物,有些频繁的操作可以不指定立即绘制,等操作完成后再统一一起绘制,效率更高。
  27. 默认开启内存瓦片机制,所有加载过的瓦片文件都存储在内存中,下次再次绘制直接从内存取出来绘制,既不需要从联网获取,也不需要从缓存文件获取,直接内存取出来绘制,响应迅速效率最高体验最佳。
  28. 支持批量添加覆盖物,比如几万个标注点和圆形,都是瞬间完成绘制,相比web网页的方式,性能提升百倍以上。
  29. 支持街道图、卫星图、混合图、路网图等各种图层,可以任意叠加N个图层,甚至杂交不同地图厂家的瓦片文件。
  30. 内置缩放动画效果,在缩放的时候先截取上一个地图画面作为缩放动画基准图片,逐步过渡到下一个层级。
  31. 内置电子放大机制,瓦片图片不存在的时候,优先获取该区域上一图层的瓦片,按照规则裁切图片缩放填充,支持无极缩放。在线和离线模式都支持电子放大,在线情况下主动获取上一级的瓦片,离线情况下主动本地文件查找。
  32. 电子放大除了支持找上级瓦片,还支持找下级瓦片,比如地图缩小的时候,先找4张下级瓦片按照规则合并到对应区域瓦片,防止缩小的时候看到空白区域。
  33. 支持双指捏合缩放,在移动端或者任何可以触摸的地方,双指拉大放大地图,双指聚拢缩小地图,平滑动画过渡缩放。
  34. 提供离线地图下载模块,多线程高速并发下载,可框选指定区域下载瓦片,可指定任意级别下载。
  35. 提供高程海拔显示模块,离线识别高程海拔数据,传入经纬度即可,无需引入任何第三方模块。
  36. 纯QWidget绘制,非qml也非web,不依赖qml或者浏览器控件,没有任何第三方库依赖,支持极低性能的嵌入式环境。
  37. 原创轻量级,核心代码5000行,整体代码10000行,架构漂亮,注释详细,拓展方便,容易学习,适合各种初学者和进阶者,方便二次开发。
  38. 支持任意Qt版本、任意系统、任意编译器,包括嵌入式linux和各种国产电脑环境。
  39. 全部原创自研,古法编程,不含任何AI代码,持续迭代更新,商业级别、品质保证。
posted @ 2026-09-29 11:29  飞扬青云  阅读(38)  评论(0)    收藏  举报