记一个优化改良的实施过程/纯QPainter实现的地图组件/支持天地图谷歌地图等

0 阅读10分钟

一、前言说明

近期在疯狂测试地图组件,在不断的放大和缩小地图过程中,一开始发现会有白色背景闪烁,尽管是一闪而过,总体感觉还是不大好,最后发现是在重新刷新界面的时候,有一次是空白透明的背景图,也触发了一次绘制,将这个绘制关闭就好,这个现象在加载本地文件的时候非常明显。关闭之后白色背景是没有了,又出来新的问题,发现缩放滑块会有个白色背景,通过打印调试发现,父类的painter触发了子类的painter导致的,而这个缩放滑块在默认绘制的时候,会以系统的背景颜色绘制,然后才是自身的绘制,本来控件是透明背景的,之前由于这个背景和闪烁的背景颜色一致,所以看不清,现在把瓦片地图的白色背景去掉以后,就很明显了。

着手解决这个问题,用了浑身解数,包括设置setAttribute(Qt::WA_OpaquePaintEvent)或者setAttribute(Qt::WA_NoSystemBackground)等,测试了十几种方法,依然无效,这个该死的白色背景还是会在一瞬间显示,也就在缩放级别变化的时候触发,后面想起来有一个setUpdatesEnabled属性可以临时禁用控件绘制,于是在缩放变化前,主动设置m_zoom_control_slider.setUpdatesEnabled(false),然后等到整张瓦片地图绘制完成的时候,再m_zoom_control_slider.setUpdatesEnabled(true)就可以了,核心原理就是在中途绘制期间禁止这个缩放滑块的绘制。但是又不能一直禁用,因为还有缩放级别要显示在滑块上,所以要在合适的时机重新启用绘制。

二、效果图

在这里插入图片描述 在这里插入图片描述

三、相关代码

#include "mapwidget.h"
#include "maphelper.h"
#include "imagemanager.h"
#include "layermapadapter.h"
#include "overlayhelper.h"

MapWidget::MapWidget(QWidget *parent) : QWidget(parent)
{
    tileSource = TileSource_Tian;
    tileType = TileType_Street;

    offline = false;
    offlinePath = ".";

    cacheExpiry = 0;
    cachePath = "";

    //实例化地图控件
    mapControl = new QMapControl(this);
    //mapControl->setMouseButtonLeft(QMapControl::MouseButtonMode::DrawBox, true);
    connect(mapControl, SIGNAL(mouseEventPressCoordinate(QMouseEvent *, PointWorldCoord)), this, SLOT(mouseEventPressCoordinate(QMouseEvent *, PointWorldCoord)));
    connect(mapControl, SIGNAL(mouseEventMoveCoordinate(QMouseEvent *, PointWorldCoord, PointWorldCoord)), this, SLOT(mouseEventMoveCoordinate(QMouseEvent *, PointWorldCoord, PointWorldCoord)));
    connect(mapControl, SIGNAL(mouseEventReleaseCoordinate(QMouseEvent *, PointWorldCoord, PointWorldCoord)), this, SLOT(mouseEventReleaseCoordinate(QMouseEvent *, PointWorldCoord, PointWorldCoord)));
}

void MapWidget::setFlag(const QString &flag)
{
    mapControl->setFlag(flag);
    this->setObjectName(QString("MapWidget_%1").arg(flag));
}

void MapWidget::resizeEvent(QResizeEvent *)
{
    QSize size = this->size();
    mapControl->setGeometry(0, 0, size.width(), size.height());

    //可以加大后面的值/会缓存更多的瓦片
    mapControl->setViewportSize(size + QSize(0, 0));
}

void MapWidget::mouseEventPressCoordinate(QMouseEvent *mouse_event, PointWorldCoord press_coordinate)
{
    emit receivePoint(press_coordinate.longitude(), press_coordinate.latitude());
}

void MapWidget::mouseEventMoveCoordinate(QMouseEvent *mouse_event, PointWorldCoord press_coordinate, PointWorldCoord current_coordinate)
{
    emit receivePoint(current_coordinate.longitude(), current_coordinate.latitude());
}

void MapWidget::mouseEventReleaseCoordinate(QMouseEvent *mouse_event, PointWorldCoord press_coordinate, PointWorldCoord release_coordinate)
{

}

void MapWidget::load()
{
    //清空缓存图片
    mapControl->clearImage();
    //清空所有图层
    mapControl->clearLayers();
    //启用缩放背景
    mapControl->enableScaledBackground(false);
    //添加瓦片图层
    TileHelper::loadTile(mapControl, tileSource, tileType, offline);

    //添加覆盖物图层
    overlayLayer = std::make_shared<LayerOverlay>("overlayLayer");
    overlayLayer->setMouseEventsEnabled(false);
    mapControl->addLayer(overlayLayer);
}

void MapWidget::zoomIn()
{
    mapControl->zoomIn();
}

void MapWidget::zoomOut()
{
    mapControl->zoomOut();
}

void MapWidget::requestRedraw()
{
    mapControl->requestRedraw();
}

int MapWidget::getZoom()
{
    return mapControl->getCurrentZoom();
}

void MapWidget::setZoom(int zoom)
{
    mapControl->setZoom(zoom);
}

QPointF MapWidget::getCenter()
{
    RectWorldCoord coord = mapControl->getViewportRect();
    return coord.rawRect().center();
}

void MapWidget::setCenter(qreal lng, qreal lat)
{
    mapControl->setMapFocusPoint(PointWorldCoord(lng, lat));
}

QRectF MapWidget::getViewportRect()
{
    return mapControl->getViewportRect().rawRect();
}

QMapControl *MapWidget::getMapControl()
{
    return this->mapControl;
}

std::shared_ptr<LayerOverlay> MapWidget::getOverlayLayer()
{
    return this->overlayLayer;
}

QMultiMap<QString, std::shared_ptr<OverlayBase>> &MapWidget::getOverlays()
{
    return this->overlays;
}

void MapWidget::enableScalebar(bool visible)
{
    mapControl->enableScalebar(visible);
}

void MapWidget::enableCrosshairs(bool visible)
{
    mapControl->enableCrosshairs(visible);
}

void MapWidget::enableZoomControls(bool visible)
{
    mapControl->enableZoomControls(visible);
}

void MapWidget::enableDragging(bool enable)
{
    mapControl->enableDragging(enable);
}

void MapWidget::enableKeyboard(bool enable)
{
    mapControl->enableKeyboard(enable);
}

void MapWidget::enableScrollWheelZoom(bool enable)
{
    mapControl->enableScrollWheelZoom(enable);
}

void MapWidget::enableDoubleClickZoom(bool enable)
{
    mapControl->enableDoubleClickZoom(enable);
}

void MapWidget::enableMouseTracking(bool enable)
{
    mapControl->setMouseTracking(enable);
}

void MapWidget::setTileSource(TileSource tileSource)
{
    this->tileSource = tileSource;
}

void MapWidget::setTileType(TileType tileType)
{
    this->tileType = tileType;
}

void MapWidget::setOffline(bool offline)
{
    this->offline = offline;
}

void MapWidget::setOfflinePath(const QString &offlinePath)
{
    this->offlinePath = offlinePath;
}

void MapWidget::setCacheExpiry(int cacheExpiry)
{
    if (this->cacheExpiry != cacheExpiry) {
        this->cacheExpiry = cacheExpiry;
        mapControl->enablePersistentCache(cachePath, std::chrono::minutes(cacheExpiry));
    }
}

void MapWidget::setCachePath(const QString &cachePath)
{
    if (this->cachePath != cachePath) {
        this->cachePath = cachePath;
        mapControl->enablePersistentCache(cachePath, std::chrono::minutes(cacheExpiry));
    }
}

void MapWidget::setStyle(const QColor &color)
{
    mapControl->setStyle(color);
}

void MapWidget::savePixmap(const QString &fileName, bool full, const QList<QPointF> &points)
{
    MapHelper::savePixmap(mapControl, fileName, full, points);
}

void MapWidget::setAutoView(const QRectF &rect)
{
    MapHelper::setAutoView(mapControl, rect);
}

void MapWidget::setAutoView(const QString &points)
{
    MapHelper::setAutoView(mapControl, MapHelper::getRect(points));
}

void MapWidget::setAutoView(const QList<QPointF> &points)
{
    MapHelper::setAutoView(mapControl, MapHelper::getRect(points));
}

void MapWidget::addPoint(const QString &flag, qreal lng, qreal lat, const QColor &color, int width)
{
    OverlayHelper::addPoint(this, flag, lng, lat, color, width);
}

void MapWidget::updatePoint(const QString &flag, qreal lng, qreal lat, const QColor &color, int width)
{
    OverlayHelper::updatePoint(this, flag, lng, lat, color, width);
}

QPointF MapWidget::getPointCoord(const QString &flag)
{
    return OverlayHelper::getPointCoord(this, flag);
}

void MapWidget::addLabel(const QString &flag, qreal lng, qreal lat, const QColor &color, const QString &text, int align, int size)
{
    OverlayHelper::addLabel(this, flag, lng, lat, color, text, align, size);
}

void MapWidget::updateLabel(const QString &flag, qreal lng, qreal lat, const QColor &color, const QString &text, int align, int size)
{
    OverlayHelper::updateLabel(this, flag, lng, lat, color, text, align, size);
}

QPointF MapWidget::getLabelPara(const QString &flag, QString *text)
{
    return OverlayHelper::getLabelPara(this, flag, text);
}

OverlayBase *MapWidget::addMarker(const QString &flag, qreal lng, qreal lat, const QString &file, const QPixmap &pixmap, int rotate, int align)
{
    return OverlayHelper::addMarker(this, flag, lng, lat, file, pixmap, rotate, align);
}

OverlayBase *MapWidget::updateMarker(const QString &flag, qreal lng, qreal lat, const QString &file, const QPixmap &pixmap, int rotate, int align)
{
    return OverlayHelper::updateMarker(this, flag, lng, lat, file, pixmap, rotate, align);
}

QPixmap MapWidget::getMarkerPara(const QString &flag, qreal *lng, qreal *lat, int *rotate, int *align)
{
    return OverlayHelper::getMarkerPara(this, flag, lng, lat, rotate, align);
}

void MapWidget::addPolyline(const QString &flag, const QStringList &points, const QColor &color, int width)
{
    OverlayHelper::addPolyline(this, flag, MapHelper::getPoints(points), color, width);
}

void MapWidget::addPolyline(const QString &flag, const QList<QPointF> &points, const QColor &color, int width)
{
    OverlayHelper::addPolyline(this, flag, MapHelper::getPoints(points), color, width);
}

void MapWidget::updatePolyline(const QString &flag, const QStringList &points, const QColor &color, int width)
{
    OverlayHelper::updatePolyline(this, flag, MapHelper::getPoints(points), color, width);
}

void MapWidget::updatePolyline(const QString &flag, const QList<QPointF> &points, const QColor &color, int width)
{
    OverlayHelper::updatePolyline(this, flag, MapHelper::getPoints(points), color, width);
}

QList<QPointF> MapWidget::getPolylinePoints(const QString &flag)
{
    return OverlayHelper::getPolylinePoints(this, flag);
}

void MapWidget::addPolylineData(const QString &flag, qreal lng, qreal lat)
{
    OverlayHelper::addPolylineData(this, flag, lng, lat);
}

void MapWidget::setPolylineData(const QString &flag, const QList<QPointF> &points)
{
    OverlayHelper::setPolylineData(this, flag, MapHelper::getPoints(points));
}

void MapWidget::addPolygon(const QString &flag, const QStringList &points, const QColor &color, int width, const QColor &bgColor)
{
    OverlayHelper::addPolygon(this, flag, MapHelper::getPoints(points), color, width, bgColor);
}

void MapWidget::addPolygon(const QString &flag, const QList<QPointF> &points, const QColor &color, int width, const QColor &bgColor)
{
    OverlayHelper::addPolygon(this, flag, MapHelper::getPoints(points), color, width, bgColor);
}

void MapWidget::updatePolygon(const QString &flag, const QStringList &points, const QColor &color, int width, const QColor &bgColor)
{
    OverlayHelper::updatePolygon(this, flag, MapHelper::getPoints(points), color, width, bgColor);
}

void MapWidget::updatePolygon(const QString &flag, const QList<QPointF> &points, const QColor &color, int width, const QColor &bgColor)
{
    OverlayHelper::updatePolygon(this, flag, MapHelper::getPoints(points), color, width, bgColor);
}

QList<QPointF> MapWidget::getPolygonPoints(const QString &flag)
{
    return OverlayHelper::getPolygonPoints(this, flag);
}

void MapWidget::addRectangle(const QString &flag, const QPointF &topLeft, const QPointF &bottomRight, const QColor &color, int width, const QColor &bgColor)
{
    QList<QPointF> points;
    QRectF rect = MapHelper::getRect(topLeft, bottomRight);
    points << rect.topLeft() << rect.topRight() << rect.bottomRight() << rect.bottomLeft();
    this->addPolygon(flag, points, color, width, bgColor);
}

void MapWidget::updateRectangle(const QString &flag, const QPointF &topLeft, const QPointF &bottomRight, const QColor &color, int width, const QColor &bgColor)
{
    QList<QPointF> points;
    if (!topLeft.isNull() && !bottomRight.isNull()) {
        QRectF rect = MapHelper::getRect(topLeft, bottomRight);
        points << rect.topLeft() << rect.topRight() << rect.bottomRight() << rect.bottomLeft();
    }

    this->updatePolygon(flag, points, color, width, bgColor);
}

QRectF MapWidget::getRectangleRect(const QString &flag)
{
    QList<QPointF> points = this->getPolygonPoints(flag);
    return QRectF(points.at(0), points.at(1));
}

void MapWidget::addBoundary(const QString &flag, const QString &points, const QColor &color, int width, const QColor &bgColor)
{
    OverlayHelper::addBoundary(this, flag, points, color, width, bgColor);
}

void MapWidget::addCircle(const QString &flag, qreal lng, qreal lat, qreal radius, const QColor &color, int width, const QColor &bgColor)
{
    OverlayHelper::addCircle(this, flag, lng, lat, radius, color, width, bgColor);
}

void MapWidget::updateCircle(const QString &flag, qreal lng, qreal lat, qreal radius, const QColor &color, int width, const QColor &bgColor)
{
    OverlayHelper::updateCircle(this, flag, lng, lat, radius, color, width, bgColor);
}

QRectF MapWidget::getCirclePara(const QString &flag, qreal *lng, qreal *lat, qreal *radius)
{
    return OverlayHelper::getCirclePara(this, flag, lng, lat, radius);
}

void MapWidget::addWidget(const QString &flag, qreal lng, qreal lat, QWidget *form)
{
    OverlayHelper::addWidget(this, flag, lng, lat, form);
}

void MapWidget::updateWidget(const QString &flag, qreal lng, qreal lat)
{
    OverlayHelper::updateWidget(this, flag, lng, lat);
}

QWidget *MapWidget::getWidget(const QString &flag)
{
    return OverlayHelper::getWidget(this, flag);
}

void MapWidget::setOverlayVisible(bool visible)
{
    OverlayHelper::setOverlayVisible(this, visible);
}

void MapWidget::setOverlayVisible(const QString &flag, bool visible)
{
    OverlayHelper::setOverlayVisible(this, flag, visible);
}

void MapWidget::deleteOverlay(const QString &flag)
{
    OverlayHelper::deleteOverlay(this, flag);
}

void MapWidget::deleteGroup(const QString &group)
{
    OverlayHelper::deleteGroup(this, group);
}

void MapWidget::clearOverlay()
{
    OverlayHelper::clearOverlay(this);
}

void MapWidget::setZIndex(const QString &flag, int zindex)
{
    OverlayHelper::setZIndex(this, flag, zindex);
}

四、相关地址

  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. 支持删除单个覆盖物、删除一种类型的覆盖物、删除所有覆盖物、隐藏指定覆盖物等。
  13. 可以动态启动禁用比例尺、十字线、缩放控件、地图拖曳、键盘操作、滚轮缩放、双击放大等特性。
  14. 可以任意指定经纬度区域进行瓦片拼接保存成图片文件,也可以直接对整个可视区域或者缓存区域的地图图片文件保存。支持任意多边形轮廓保存成图片,比如某个行政区的瓦片保存。
  15. 覆盖物可以动态设置zindex层叠顺序,值越大,越显示在前面,内部维护着一个zindex表,默认按照添加的先后顺序增加,后面添加的显示在前面,主动设置后,按照设置的zindex来绘制。
  16. 支持将QWidget对象作为覆盖物添加到地图控件中,跟随地图移动位置,极大提高灵活性,比如可以将自定义控件直接作为地图控件的子对象加入进去。
  17. 大量使用按需绘制机制,包括内部提供合理的默认值来触发绘制,也可以手动传入参数指定是否需要立即绘制,比如删除了某个覆盖物,有些频繁的操作可以不指定立即绘制,等操作完成后再统一一起绘制,效率更高。
  18. 默认开启缓存瓦片机制,所有加载过的瓦片文件都存储在内存中,下次再次绘制直接从内存取出来绘制,既不需要从联网获取,也不需要从缓存文件获取,直接内存取出来绘制,响应迅速效率最高体验最佳。
  19. 支持批量添加覆盖物,比如几万个标注点和圆形,都是瞬间完成绘制,相比web网页的方式,性能提升百倍以上。
  20. 支持街道图、卫星图、混合图、路网图等各种图层,可以任意叠加N个图层,甚至杂交不同地图厂家的瓦片文件。
  21. 纯QWidget绘制,非qml也非web,不依赖qml或者浏览器控件,支持极低性能的嵌入式环境。
  22. 支持任意Qt版本、任意系统、任意编译器,包括嵌入式linux和各种国产电脑环境。