惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Engineering at Meta
Engineering at Meta
G
Google Developers Blog
WordPress大学
WordPress大学
M
MIT News - Artificial intelligence
D
DataBreaches.Net
云风的 BLOG
云风的 BLOG
爱范儿
爱范儿
Microsoft Security Blog
Microsoft Security Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Blog — PlanetScale
Blog — PlanetScale
T
Tailwind CSS Blog
S
SegmentFault 最新的问题
阮一峰的网络日志
阮一峰的网络日志
博客园 - 三生石上(FineUI控件)
酷 壳 – CoolShell
酷 壳 – CoolShell
Recent Announcements
Recent Announcements
T
The Blog of Author Tim Ferriss
I
InfoQ
MyScale Blog
MyScale Blog
V
V2EX
B
Blog
罗磊的独立博客
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More

博客园 - 飞扬青云

坚持古法编程有意义吗/纯手搓代码/监控Onvif调试神器 AI编程道心崩塌/十几年Qt开发经验一文不值 就说逆不逆天/支持高达500路监控画面/多个屏幕同时显示/轻松实现跨屏实时监控 高程海拔离线实时查询显示终极版/体验友好/全网首创极精简版本/可输入地址或者经纬度查询 Qt实现很好玩的滤镜效果/高斯模糊径向模糊/底片锐化暗角鱼眼/多点矫正几何矫正边缘融合/扭曲网格调整 震惊都6202年了/地图控件居然支持Qt4这个老古董/支持国产军工低配Qt环境 Qt代码实现地图瓦片电子放大/无极缩放/可将层级拓展到22级别/精确到0.03米 纯QWidget绘制打造地图组件/极速加载瓦片地图/放大缩小地图过渡效果 这才是地图控件该有的样子/不依赖js和qml/纯古法原创自研/纯代码绘制/最新版含泪发布 效果拉满/纯Qt实现海图地图展示/矢量底图+海图+路网标记任意多层叠加/完美支持海图叠加与多投影系 GB28181视频监控推流服务/点播预览推流/录像回放推流/指令预览推流/指令回放推流/自动预览推流 Qt电子地图叠加海图/全世界海图叠加到天地图图层/1-18级海图实时查看 超级屌的动态测距交互功能/耗时几万个脑细胞/自研原创地图控件 Qt纯原创代码实现GB28181组件/自动点播拉流并推流/支持录像回放和下载/性能凶残 全网原创再次熬夜升级/支持云台预置位的onvif设备模拟器/虚拟监控摄像头/可设置主码流子码流 使用ffmpeg播放不同节目/实时切换节目并推流/推流不同的节目流 新版监控GB28181调试神器V2026发布/对标wvp/绿色版开箱即用 对标国际Onvif工具odm/新版视频监控onvif调试助手V2026发布/简单好用开箱即用 全网唯一/不依赖浏览器和qlocation/纯代码绘制实现的地图组件/短小精悍 监控设备模拟神器V2026全新含泪发布/支持onvif和gb28181/支持云台控制和预置位 将地图组件运行在浏览器上/qtlocation都做不到/轻巧好用/自研原创 不依赖任何AI代码/纯qpainter实现的航线规划/飞行轨迹规划和模拟/无人机轨迹 如何快速搭建一套支持ONVIF的安防监控系统 没有想不到只有做不到/万能地图标注点marker设计/各种参数需求场景/你要的都有 呕心沥血/历时半年/古法编程/纯QPainter实现的地图控件/性能爆表 绘制1000个无人机只需7ms/优化到极致/地图组件海量标注点/纯QPainter绘制的威力 实时轨迹/轨迹回放/多条轨迹同时回放/轨迹平滑移动/移动速度和间隔/自动旋转角度 记一个优化改良的实施过程/纯QPainter实现的地图组件/支持天地图谷歌地图等 性能提升百倍/Qt地图组件纯QWidget绘制/缓存瓦片技术/多线程加载绘制 Qt实现离线地图下载/瓦片地图下载/划区域下载离线地图/多线程批量下载
纯QWidget绘制实现电子地图控件/非qml非web/多线程下载和加载...
飞扬青云 · 2026-02-25 · via 博客园 - 飞扬青云

一、前言说明

之前做的地图组件,耗费了巨大的时间精力,前后完善了五年之多,能够想到的应用场景几乎都实现了,也有不少的用户,现场实际需求也不断反馈,不断的修改和增加功能,尽管优点很多,依然有个巨大缺点就是依赖浏览器控件,性能肯定是要打折扣的,毕竟有些嵌入式板子甚至老的开发环境,不一定有浏览器控件,就算有,在嵌入式板子环境上或者一些国产硬件的系统上,配置比较低,在浏览器上运行的网页,性能指数级下降,甚至一些环境连GPU都没有,老板为了节省成本,尽量选一些配置低的板子,所以也没有一种可能用QWidget绘制来实现呢,这样性能极好,而且控制度极高,qt的painter非常灵活可靠。

经过大量的尝试改造,总算在今年实现了这个地图控件,不依赖浏览器控件,也不依赖qml,有些人用的Qt自带的qml的location组件来实现的,这个尽管方便,但是性能也低,因为嵌入式环境配置低的板子,根本无法正常跑起来qml,别提要新版的Qt才有qlocaltion组件。用qwidget来实现有两个最大难点,一个是如何将地理坐标映射到像素绘制坐标,一个是如何快速的加载瓦片多线程绘制,这个必须采用多个分层绘制的机制。还有一个难点是绘制的图形有两大类,一种是面积大小自适应的比如多边形,一种是不需要自适应的一直是固定大小的比如标注点,这两种都需要在固定的经纬度区域,拖动移动要自动移动的对应的位置。将地理坐标映射到像素绘制坐标这个是最难的,整个地图组件的绘制要不断的将经纬度坐标转像素坐标,识别区域要将像素坐标转经纬度坐标。

二、效果图

Snipaste_2026-02-25_09-17-52
Snipaste_2026-02-25_09-17-27

三、相关代码

#include "frmoverlay.h"
#include "ui_frmoverlay.h"
#include "qthelper.h"
#include "maphelper.h"
#include "overlayhelper.h"
#include "magicfish.h"

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

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

void frmOverlay::initForm()
{
    lng = 121.424362;
    lat = 31.175942;

    connect(ui->mapWidget, SIGNAL(receivePoint(qreal, qreal)), this, SLOT(receivePoint(qreal, qreal)));
    connect(ui->cboxFlag->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(textChanged(QString)));
}

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

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

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

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

void frmOverlay::saveConfig()
{
    AppConfig::OverlaySource = ui->cboxTileSource->itemData(ui->cboxTileSource->currentIndex()).toInt();
    AppConfig::OverlayType = ui->cboxTileType->itemData(ui->cboxTileType->currentIndex()).toInt();
    AppConfig::OverlayOffline = ui->cboxOffline->currentIndex();
    AppConfig::OverlayCache = ui->cboxCache->currentIndex();
    AppConfig::writeConfig();
}

void frmOverlay::reset()
{
    index = 1;
    flag = "overlay1";
    ui->cboxFlag->clear();
    ui->cboxFlag->lineEdit()->setText(flag);
}

void frmOverlay::loadMap()
{
    this->reset();

    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);
}

void frmOverlay::receivePoint(qreal lng, qreal lat)
{
    this->lng = lng;
    this->lat = lat;
    ui->txtLng->setText(QString::number(lng, 'f', 6));
    ui->txtLat->setText(QString::number(lat, 'f', 6));
}

void frmOverlay::addIndex()
{
    index++;
    QString text = QString("overlay%1").arg(index);
    QString item = QString("overlay%1").arg(index - 1);
    ui->cboxFlag->addItem(item);
    ui->cboxFlag->lineEdit()->setText(text);
}

void frmOverlay::textChanged(const QString &arg1)
{
    if (!arg1.isEmpty()) {
        this->flag = arg1;
    }
}

void frmOverlay::on_btnAddMarker_clicked()
{
    QString file = ":/image/marker.png";
    //QString file = ":/image/gif_person.gif";
    OverlayBase *marker = ui->mapWidget->addMarker(flag, lng, lat, file);
    OverlayHelper::updateText(marker, "标注点文本", "#000000", 4, 0, "");
    this->addIndex();
}

void frmOverlay::on_btnDeleteMarker_clicked()
{
    ui->mapWidget->deleteOverlay(flag);
}

void frmOverlay::on_btnUpdateMarker_clicked()
{
    QString file = ":/image/gif_person.gif";
    OverlayBase *marker = ui->mapWidget->updateMarker(flag, lng, lat, file);
    OverlayHelper::updateText(marker, "新的文本底部居中", "#ff0000", 8, 0, "", 20);
}

void frmOverlay::on_btnRotateMarker_clicked()
{
    ui->mapWidget->updateMarker(flag, NULL, NULL, QString(), QPixmap(), 30);
}

void frmOverlay::on_btnAddLabel_clicked()
{
    ui->mapWidget->addLabel(flag, lng, lat, "#ff0000", "测试文本", 2);
    this->addIndex();
}

void frmOverlay::on_btnUpdateLabel_clicked()
{
    ui->mapWidget->updateLabel(flag, lng, lat, "#00ffff", "新的文本", 8, 20);
}

void frmOverlay::on_btnAddPolyline_clicked()
{
    QPointF point(lng, lat);
    QList<QPointF> points = MapHelper::getRandPoints(point, 4, 0.1);
    ui->mapWidget->addPolyline(flag, points, "#ff0000", 3);
    this->addIndex();
}

void frmOverlay::on_btnUpdatePolyline_clicked()
{
    ui->mapWidget->updatePolyline(flag, QStringList(), QColor(0, 100, 100, 180), 5);
}

void frmOverlay::on_btnAddPolygon_clicked()
{
    QPointF point(lng, lat);
    QList<QPointF> points = MapHelper::getRandPoints(point, 3, 0.15);
    ui->mapWidget->addPolygon(flag, points, "#00ff00", 2, QColor(0, 255, 0, 100));
    this->addIndex();
}

void frmOverlay::on_btnUpdatePolygon_clicked()
{
    ui->mapWidget->updatePolygon(flag, QStringList(), Qt::yellow, 4, QColor(250, 200, 89, 150));
}

void frmOverlay::on_btnAddRectangle_clicked()
{
    QPointF point(lng, lat);
    QList<QPointF> points = MapHelper::getRandPoints(point, 1, 0.15);
    ui->mapWidget->addRectangle(flag, points.at(0), points.at(1), "#008888", 2, QColor(0, 255, 0, 100));
    this->addIndex();
}

void frmOverlay::on_btnUpdateRectangle_clicked()
{
    ui->mapWidget->updateRectangle(flag, QPointF(), QPointF(), Qt::red, 3, QColor(255, 0, 89, 150));
}

void frmOverlay::on_btnAddCircle_clicked()
{
    //根据当前缩放级别获取合适的长度
    int distance = MapHelper::getDistance(ui->mapWidget->getZoom());
    qreal radius = MapHelper::getRadius(distance);
    ui->mapWidget->addCircle(flag, lng, lat, radius, "#0000ff", 3, QColor(0, 0, 255, 50));
    this->addIndex();
}

void frmOverlay::on_btnUpdateCircle_clicked()
{
    qreal radius = MapHelper::getRadius(8000);
    ui->mapWidget->updateCircle(flag, lng, lat, radius, "#ff0000");
}

void frmOverlay::on_btnAddWidget_clicked()
{
    MagicFish *widget = new MagicFish(ui->mapWidget);
    ui->mapWidget->addWidget(flag, lng, lat, widget);
    this->addIndex();
}

void frmOverlay::on_btnUpdateWidget_clicked()
{
    ui->mapWidget->updateWidget(flag, lng, lat);
}

void frmOverlay::on_btnAddBoundary_clicked()
{
    on_btnClearOverlay_clicked();
    QString fileName = QtHelper::getOpenFileName("*.txt", QtHelper::appPath() + "/mapboundary");
    if (!fileName.isEmpty()) {
        QFile file(fileName);
        if (file.open(QFile::ReadOnly | QFile::Text)) {
            QString points = file.readAll();
            ui->mapWidget->addBoundary("boundary", points, "#ff55ff", 2, QColor(255, 100, 255, 100));
        }
    }
}

void frmOverlay::on_btnAddOverlays_clicked()
{
    //批量添加可以先禁用重绘
    OverlayHelper::redraw = false;

    on_btnClearOverlay_clicked();
    ui->mapWidget->setZoom(10);
    ui->mapWidget->setCenter(121.424362, 31.175942);

    for (int i = 0; i < 1000; ++i) {
        qreal lng = QtHelper::getRandFloat(121.135250, 121.77727);
        qreal lat = QtHelper::getRandFloat(30.99056, 31.43569);
        qreal r = QtHelper::getRandFloat(0.02, 0.07);
        QColor color = QtHelper::getRandColor();
        QColor bgColor = color;
        bgColor.setAlpha(150);
        ui->mapWidget->addCircle(QString("circle%1").arg(i), lng, lat, r, color, 3, bgColor);
    }

    //添加完成后启用重绘
    OverlayHelper::redraw = true;
}

void frmOverlay::on_btnDeleteOverlay_clicked()
{
    ui->mapWidget->deleteOverlay(flag);
}

void frmOverlay::on_btnDeleteGroup_clicked()
{
    ui->mapWidget->deleteGroup("circle");
}

void frmOverlay::on_btnClearOverlay_clicked()
{
    this->reset();
    ui->mapWidget->clearOverlay();
}

void frmOverlay::on_btnVisibleOverlay_clicked()
{
    if (ui->btnVisibleOverlay->text() == "显示所有") {
        ui->mapWidget->setOverlayVisible("", true);
        ui->btnVisibleOverlay->setText("隐藏所有");
    } else {
        ui->mapWidget->setOverlayVisible("", false);
        ui->btnVisibleOverlay->setText("显示所有");
    }
}

四、相关地址

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

五、功能特点

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