效果拉满/纯Qt实现海图地图展示/矢量底图+海图+路网标记任意多层叠加/完美支持海图叠加与多投影系

一、前言说明

前期投入大量精力,基于纯QWidget和Painter绘制了一套电子地图控件。该控件已具备成熟的多级图层叠加能力,底层支持天地图、高德等瓦片地图,顶层图形绘制层可灵活渲染折线、多边形、矩形及圆形等覆盖物。
近期针对社区高频询问的“海图支持”需求,完成了核心功能的研发。在数据源方面,考虑到部分厂商的限制,最终选用了免费且体验极佳的天地图作为海图瓦片源。其不仅采用标准的GPS坐标系,卫星图更支持至18级,且瓦片下载后支持离线部署。
为适配在线海图瓦片,本次对底层控件进行了深度重构:
投影系解耦:打破原有单一的墨卡托投影限制,支持为每个控件实例独立设置投影类型,完美兼容经纬度投影。
瓦片尺寸兼容:在原有256像素标准的基础上,新增对512像素瓦片的渲染支持。
经过多轮测试与调整,目前控件已实现天地图海图的无缝叠加。同时支持任意底图组合,例如构建“矢量底图 + 海图 + 矢量标记/路网图”的三层叠加架构,大幅提升了地图展示的完整性与专业度。

二、效果图

Snipaste_2026-08-03_09-34-35

三、相关代码

#include "layermapadapter.h"
#include "imagemanager.h"
#include "imagehelper.h"

LayerMapAdapter::LayerMapAdapter(const QString &name, MapAdapter *mapadapter, int min_zoom, int max_zoom, QObject *parent)
    : LayerBase(name, LayerType_MapAdapter, min_zoom, max_zoom, parent),
      m_bgColor(Qt::transparent),
      m_mapadapter(mapadapter)
{
    m_image_manager = NULL;
}

LayerMapAdapter::~LayerMapAdapter()
{

}

MapAdapter *LayerMapAdapter::mapAdapter()
{
    QReadLocker locker(&m_mutex);
    return m_mapadapter;
}

void LayerMapAdapter::setMapAdapter(MapAdapter *mapadapter)
{
    {
        QWriteLocker locker(&m_mutex);
        m_mapadapter = mapadapter;
    }

    emit requestRedraw();
}

void LayerMapAdapter::mouseEvent(MapControl *map_control, int mouse_type, const PointWorldCoord &mouse_coord, int cur_zoom) const
{

}

void LayerMapAdapter::drawXYZ(QPainter *painter, const QPointF &point, int x, int y, int z) const
{
    //绘制对应xyz值
    QTextOption option;
    option.setAlignment(Qt::AlignCenter);
    option.setWrapMode(QTextOption::WordWrap);
    QString text = QString("x=%1\ny=%2\nz=%3").arg(x).arg(y).arg(z);
    QRectF rect(point.x(), point.y(), m_tile_size, m_tile_size);

    painter->save();
    QFont font = painter->font();
    font.setPixelSize(50);
    painter->setFont(font);
    painter->setPen(Qt::red);
    painter->drawText(rect, text, option);
    painter->drawRect(rect);
    painter->restore();
}

void LayerMapAdapter::draw(QPainter *painter, const RectWorldPx &backbuffer_rect_px, int cur_zoom) const
{
    QReadLocker locker(&m_mutex);

    //没有设置或者不可见则不用继续
    if (!m_mapadapter || !m_image_manager || !isVisible(cur_zoom)) {
        return;
    }

    //没有设置瓦片地址也不用继续
    if (m_mapadapter->url().isEmpty()) {
        return;
    }

    //绘制背景颜色/形成样式风格
    if (m_bgColor != Qt::transparent) {
        painter->fillRect(backbuffer_rect_px.rawRect(), m_bgColor);
    }

    //计算瓦片绘制区域
    QSize tile_size(m_tile_size, m_tile_size);
    int furthest_tile_left = qFloor(backbuffer_rect_px.leftPx() / tile_size.width());
    int furthest_tile_top = qFloor(backbuffer_rect_px.topPx() / tile_size.height());
    int furthest_tile_right = qFloor(backbuffer_rect_px.rightPx() / tile_size.width());
    int furthest_tile_bottom = qFloor(backbuffer_rect_px.bottomPx() / tile_size.height());

    //绘制可视区域内的瓦片
    for (int i = furthest_tile_left; i <= furthest_tile_right; ++i) {
        for (int j = furthest_tile_top; j <= furthest_tile_bottom; ++j) {
            if (!m_mapadapter->isTileValid(i, j, cur_zoom)) {
                continue;
            }

            PointWorldPx top_left_px(i * tile_size.width(), j * tile_size.height());
            QPointF point = top_left_px.rawPoint();
            QPixmap pixmap = m_image_manager->getImage(m_mapadapter->tileQuery(i, j, cur_zoom));

            //可能只是去请求了瓦片/而并没有真正获取到图片
            //可能需要缩放图片/256像素的地图/请求的512像素的瓦片
            if (!pixmap.isNull()) {
                if (pixmap.size() != tile_size) {
                    pixmap = pixmap.scaled(tile_size);
                    //pixmap = pixmap.scaled(tile_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
                }

                painter->drawPixmap(point, pixmap);
            }

            if (ImageHelper::drawTileXYZ) {
                this->drawXYZ(painter, point, i, j, cur_zoom);
            }
        }
    }

    //计算可视区域外的范围
    //预加载可视区域外的瓦片/四周不可见区域/默认会预加载四周的/方便拖动快速绘制
    int prefetch_tile_offset = 0;
    int prefetch_tile_left = furthest_tile_left - prefetch_tile_offset;
    int prefetch_tile_top = furthest_tile_top - prefetch_tile_offset;
    int prefetch_tile_right = furthest_tile_right + prefetch_tile_offset;
    int prefetch_tile_bottom = furthest_tile_bottom + prefetch_tile_offset;

    //预加载可视区域外上侧和下侧部分
    for (int i = prefetch_tile_left; i <= prefetch_tile_right; ++i) {
        if (m_mapadapter->isTileValid(i, prefetch_tile_top, cur_zoom)) {
            m_image_manager->prefetchImage(m_mapadapter->tileQuery(i, prefetch_tile_top, cur_zoom));
        }
        if (m_mapadapter->isTileValid(i, prefetch_tile_bottom, cur_zoom)) {
            m_image_manager->prefetchImage(m_mapadapter->tileQuery(i, prefetch_tile_bottom, cur_zoom));
        }
    }

    //预加载可视区域外左侧和右侧部分
    for (int j = prefetch_tile_top; j <= prefetch_tile_bottom; ++j) {
        if (m_mapadapter->isTileValid(prefetch_tile_left, j, cur_zoom)) {
            m_image_manager->prefetchImage(m_mapadapter->tileQuery(prefetch_tile_left, j, cur_zoom));
        }
        if (m_mapadapter->isTileValid(prefetch_tile_right, j, cur_zoom)) {
            m_image_manager->prefetchImage(m_mapadapter->tileQuery(prefetch_tile_right, j, cur_zoom));
        }
    }
}

void LayerMapAdapter::setBgColor(const QColor &bgColor)
{
    if (m_bgColor != bgColor) {
        m_bgColor = bgColor;
        emit requestRedraw();
    }
}

void LayerMapAdapter::setImageManager(ImageManager *imageManager)
{
    this->m_image_manager = imageManager;
}

四、相关地址

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