绘制1000个无人机只需7ms/优化到极致/地图组件海量标注点/纯QPainter绘制的威力

0 阅读8分钟

一、前言说明

现在流行无人机和机器人,未来这块的增量是巨大的,地图组件也顺势而为,之前用web版本的地图组件绘制100个无人机,还行,就是耗时比较多,而且占用内存较大,性能较低,如果是1000个,那基本上歇菜,根本都无法完成,毕竟在web的地图组件中,marker对象是一个很多参数的对象,每一个都需要分配不少的内存,不适合大量的对象,在花了巨大精力用纯QPainter实现这个widget版本的地图组件,本着百倍性能提升的目标去的,亲自验证了下,1000个无人机只需要7ms就完成,不要太好,可以说是瞬间完成,各位web程序员还把web吹上天,在很多大量绘制场景中,还是必须cs架构的软件更擅长。

二、效果图

在这里插入图片描述

三、相关代码

#include "frmmarker.h"
#include "ui_frmmarker.h"
#include "qthelper.h"
#include "maphelper.h"
#include "maputil.h"
#include "overlaybase.h"
#include "overlayhelper.h"

frmMarker::frmMarker(QWidget *parent) : QWidget(parent), ui(new Ui::frmMarker)
{
    ui->setupUi(this);
    this->initForm();
    this->initConfig();
    this->loadMap();
}

frmMarker::~frmMarker()
{
    delete ui;
}

void frmMarker::initForm()
{
    offset = 5;
    flag = "marker";
    marker = NULL;

    ui->mapWidget->setFlag("frmMarker");
    connect(ui->mapWidget, SIGNAL(receivePoint(qreal, qreal)), this, SLOT(receivePoint(qreal, qreal)));
}

void frmMarker::initConfig()
{
    MapHelper::loadTileSource(ui->cboxTileSource, AppConfig::MarkerSource);
    connect(ui->cboxTileSource, SIGNAL(currentIndexChanged(int)), this, SLOT(saveConfig()));
    connect(ui->cboxTileSource, SIGNAL(currentIndexChanged(int)), this, SLOT(loadMap()));

    MapHelper::loadTileType(ui->cboxTileType, AppConfig::MarkerType);
    connect(ui->cboxTileType, SIGNAL(currentIndexChanged(int)), this, SLOT(saveConfig()));
    connect(ui->cboxTileType, SIGNAL(currentIndexChanged(int)), this, SLOT(loadMap()));

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

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

    MapHelper::loadAlignType(ui->cboxMarkerAlign, 5);
    MapHelper::loadAlignType(ui->cboxTextAlign, 4);
    ui->cboxFontSize->setCurrentIndex(2);
    ui->cboxNumber->setCurrentIndex(2);
    ui->cboxTextColor->setColorName("red");
    ui->cboxTextBgColor->setColorName("black");
    ui->cboxTextBorderColor->setColorName("blue");
}

void frmMarker::saveConfig()
{
    AppConfig::MarkerSource = ui->cboxTileSource->itemData(ui->cboxTileSource->currentIndex()).toInt();
    AppConfig::MarkerType = ui->cboxTileType->itemData(ui->cboxTileType->currentIndex()).toInt();
    AppConfig::MarkerOffline = ui->cboxOffline->currentIndex();
    AppConfig::MarkerCache = ui->cboxCache->currentIndex();
    AppConfig::writeConfig();
}

void frmMarker::loadMap()
{
    on_btnClearMarker_clicked();

    int tileSource = ui->cboxTileSource->itemData(ui->cboxTileSource->currentIndex()).toInt();
    ui->mapWidget->setTileSource(TileSource(tileSource));

    int tileType = ui->cboxTileType->itemData(ui->cboxTileType->currentIndex()).toInt();
    ui->mapWidget->setTileType(TileType(tileType));

    QString offlinePath = TileHelper::getOfflinePath(TileSource(tileSource));
    ui->mapWidget->setOffline(ui->cboxOffline->currentIndex() == 1);
    ui->mapWidget->setOfflinePath(offlinePath);

    QString cachePath = (ui->cboxCache->currentIndex() == 1 ? QtHelper::appPath() + "/mapcache" : "");
    ui->mapWidget->setCachePath(cachePath);
    ui->mapWidget->load();
}

void frmMarker::receivePoint(qreal lng, qreal lat)
{
    ui->txtLng->setText(QString::number(lng, 'f', 6));
    ui->txtLat->setText(QString::number(lat, 'f', 6));
    ui->mapWidget->updateMarker(flag, lng, lat);
    ui->mapWidget->updateShape("shape", lng, lat);
}

QString frmMarker::getImage()
{
    int index = ui->cboxMarkerType->currentIndex();
    QString image = ":/image/marker.png";
    if (index == 1) {
        image = ":/image/fly.png";
    } else if (index == 2) {
        image = ":/image/gif_person.gif";
    }

    return image;
}

void frmMarker::on_btnCreateMarker_clicked()
{
    if (!marker) {
        qreal lng = ui->txtLng->text().toDouble();
        qreal lat = ui->txtLat->text().toDouble();
        QString image = this->getImage();

        //创建标注点对象
        int rotate = ui->sboxRotate->value();
        int align = ui->cboxMarkerAlign->itemData(ui->cboxMarkerAlign->currentIndex()).toInt();
        marker = ui->mapWidget->addMarker(flag, lng, lat, image, QPixmap(), rotate, align);

        //更新提示信息参数
        QString text = ui->txtText->text();
        QColor textColor = ui->cboxTextColor->getColorName();
        int textAlign = ui->cboxTextAlign->itemData(ui->cboxTextAlign->currentIndex()).toInt();
        int fontSize = ui->cboxFontSize->currentText().toInt();
        QColor bgColor = ui->cboxTextBgColor->getColorName();
        int bgAlpha = ui->sboxTextBgAlpha->value();
        QColor borderColor = ui->cboxTextBorderColor->getColorName();
        OverlayHelper::updateText(marker, text);
        OverlayHelper::updateTextPara(marker, textColor, textAlign, offset, QString(), fontSize, bgColor, bgAlpha, borderColor);

        //添加原始位置参照
        ui->mapWidget->addShape("shape", lng, lat, textColor, 8);
    }
}

void frmMarker::on_btnCreateMarkers_clicked()
{
    //先清空所有
    on_btnClearMarker_clicked();
    ui->btnCreateMarkers->setEnabled(false);

    //以地图左下角为起点/随机生成多个点坐标
    QString image = ":/image/fly.png";
    int number = ui->cboxNumber->currentText().toInt();
    QList<QPointF> points = MapHelper::getRandPoints(ui->mapWidget->getRect(), number);
    foreach (QPointF point, points) {
        int rotate = rand() % 360;
        ui->mapWidget->addMarker("markers", point.x(), point.y(), image, QPixmap(), rotate, 2);
    }

    ui->btnCreateMarkers->setEnabled(true);
}

void frmMarker::on_btnClearMarker_clicked()
{
    marker = NULL;
    ui->mapWidget->clearOverlay();
    //ui->mapWidget->deleteGroup("shape");
    //ui->mapWidget->deleteGroup("marker");
}

void frmMarker::on_cboxMarkerType_currentIndexChanged(int index)
{
    QString image = this->getImage();
    ui->mapWidget->updateMarker(flag, NULL, NULL, image);
}

void frmMarker::on_cboxMarkerAlign_currentIndexChanged(int index)
{
    int align = ui->cboxMarkerAlign->itemData(index).toInt();
    ui->mapWidget->updateMarker(flag, NULL, NULL, QString(), QPixmap(), -1, align);
}

void frmMarker::on_sboxRotate_valueChanged(int arg1)
{
    ui->mapWidget->updateMarker(flag, NULL, NULL, QString(), QPixmap(), arg1, -1);
}

void frmMarker::on_txtText_textChanged(const QString &arg1)
{
    OverlayHelper::updateText(marker, arg1);
}

void frmMarker::on_cboxTextColor_currentIndexChanged(int index)
{
    QColor textColor = ui->cboxTextColor->getColorName();
    OverlayHelper::updateTextPara(marker, textColor);
    ui->mapWidget->updateShape("shape", NULL, NULL, textColor);
}

void frmMarker::on_cboxFontSize_currentIndexChanged(int index)
{
    int fontSize = ui->cboxFontSize->currentText().toInt();
    OverlayHelper::updateTextPara(marker, QColor(), -1, offset, QString(), fontSize);
}

void frmMarker::on_cboxTextAlign_currentIndexChanged(int index)
{
    int textAlign = ui->cboxTextAlign->itemData(index).toInt();
    OverlayHelper::updateTextPara(marker, QColor(), textAlign, offset, QString(), 0);
}

void frmMarker::on_cboxTextBgColor_currentIndexChanged(int index)
{
    QColor bgColor = ui->cboxTextBgColor->getColorName();
    OverlayHelper::updateTextPara(marker, QColor(), -1, offset, QString(), 0, bgColor, 0, QColor(), 0);
}

void frmMarker::on_sboxTextBgAlpha_valueChanged(int arg1)
{
    int bgAlpha = ui->sboxTextBgAlpha->value();
    OverlayHelper::updateTextPara(marker, QColor(), -1, offset, QString(), 0, QColor(), bgAlpha, QColor(), 0);
}

void frmMarker::on_cboxTextBorderColor_currentIndexChanged(int index)
{
    QColor borderColor = ui->cboxTextBorderColor->getColorName();
    OverlayHelper::updateTextPara(marker, QColor(), -1, offset, QString(), 0, QColor(), 0, borderColor, 0);
}

四、相关地址

  1. 国内站点:gitee.com/feiyangqing…
  2. 国际站点:github.com/feiyangqing…
  3. 个人作品:blog.csdn.net/feiyangqing…
  4. 文件地址:pan.baidu.com/s/1ZxG-oyUK… 提取码:o05q 文件名:bin_mapwidget.zip

五、功能特点

  1. 支持各种地图源,包括天地图、高德地图、腾讯地图、谷歌地图、微软地图等。
  2. 标准WGS-84地球坐标系,采用默卡托投影,可以拓展其他坐标系和投影规则。
  3. 支持在线和离线两种场景需求,可以自定义在线瓦片地址格式和离线瓦片地址格式。
  4. 多线程下载和加载瓦片图片文件,多线程绘制,自动缓存瓦片文件。
  5. 在线模式下,可以开启是否缓存文件,指定缓存路径,将下载的瓦片文件存放到本地,默认优先从缓存文件查找,如果存在缓存文件则加载缓存文件,不存在则联网下载。
  6. 可以拖动地图,鼠标滚轮放大和缩小地图,以鼠标所在位置作为缩放中心点,提供缩放控件手动单击进行操作。
  7. 多图层机制,支持多个瓦片叠加图层和图形绘制图层,全部采用双缓冲技术,所有的图形和瓦片全部绘制到一个图片文件上,最终再将图片文件绘制到地图控件。不可见区域的图层包括覆盖物不会触发绘制,降低CPU占用。
  8. 预加载机制,默认绘制的图层大小以当前区域往四周放大两倍,这样在鼠标拖动和缩放的时候,不会看到明显的加载过程,体验更佳。
  9. 内置了多种图形覆盖物,包括标注点、折线、多边形、矩形、圆形等,可以设置边框颜色粗细、填充颜色和透明度等参数。
  10. 标注点支持旋转角度和提示文本,其中提示文本可以设置在标注点的相对位置,标注点图片支持gif动图,可以动态切换静态图和动图。
  11. 标注点和提示文本可以设置相对位置,位置包括左侧、右侧、上侧、下侧、中间、左上角、右上角、左下角、右下角。标注点默认按照底部居中对齐,一般圆形图标可以设置中心点对齐。
  12. 标注点提示文本可设置背景颜色,透明度、颜色边框和粗细,支持html富文本。
  13. 所有的覆盖物可以动态更新前景色、颜色粗细、背景颜色、颜色透明度等。
  14. 支持删除单个覆盖物、删除一种类型的覆盖物、删除所有覆盖物、隐藏指定覆盖物等。
  15. 可以动态启动禁用比例尺、十字线、缩放控件、地图拖曳、键盘操作、滚轮缩放、双击放大等特性。
  16. 可以任意指定经纬度区域进行瓦片拼接保存成图片文件,也可以直接对整个可视区域或者缓存区域的地图图片文件保存。支持任意多边形轮廓保存成图片,比如某个行政区的瓦片保存。
  17. 覆盖物可以动态设置zindex层叠顺序,值越大,越显示在前面,内部维护着一个zindex表,默认按照添加的先后顺序增加,后面添加的显示在前面,主动设置后,按照设置的zindex来绘制。
  18. 支持将QWidget对象作为覆盖物添加到地图控件中,跟随地图移动位置,极大提高灵活性,比如可以将自定义控件直接作为地图控件的子对象加入进去。
  19. 内置moveMarker轨迹移动类,支持历史轨迹数据回放和实时轨迹移动,可设置图标、轨迹线的颜色和粗细、移动速度、移动间隔、平滑移动等,支持多条轨迹线条同时移动。
  20. 大量使用按需绘制机制,包括内部提供合理的默认值来触发绘制,也可以手动传入参数指定是否需要立即绘制,比如删除了某个覆盖物,有些频繁的操作可以不指定立即绘制,等操作完成后再统一一起绘制,效率更高。
  21. 默认开启缓存瓦片机制,所有加载过的瓦片文件都存储在内存中,下次再次绘制直接从内存取出来绘制,既不需要从联网获取,也不需要从缓存文件获取,直接内存取出来绘制,响应迅速效率最高体验最佳。
  22. 支持批量添加覆盖物,比如几万个标注点和圆形,都是瞬间完成绘制,相比web网页的方式,性能提升百倍以上。
  23. 支持街道图、卫星图、混合图、路网图等各种图层,可以任意叠加N个图层,甚至杂交不同地图厂家的瓦片文件。
  24. 纯QWidget绘制,非qml也非web,不依赖qml或者浏览器控件,支持极低性能的嵌入式环境。
  25. 支持任意Qt版本、任意系统、任意编译器,包括嵌入式linux和各种国产电脑环境。古法编程,不含任何AI代码,品质保证。