一、前言说明
近期在疯狂测试地图组件,在不断的放大和缩小地图过程中,一开始发现会有白色背景闪烁,尽管是一闪而过,总体感觉还是不大好,最后发现是在重新刷新界面的时候,有一次是空白透明的背景图,也触发了一次绘制,将这个绘制关闭就好,这个现象在加载本地文件的时候非常明显。关闭之后白色背景是没有了,又出来新的问题,发现缩放滑块会有个白色背景,通过打印调试发现,父类的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);
}
四、相关地址
- 国内站点:gitee.com/feiyangqing…
- 国际站点:github.com/feiyangqing…
- 个人作品:blog.csdn.net/feiyangqing…
- 文件地址:pan.baidu.com/s/1ZxG-oyUK… 提取码:o05q 文件名:bin_mapwidget.zip
五、功能特点
- 支持各种地图源,包括天地图、高德地图、腾讯地图、谷歌地图、微软地图等。
- 标准WGS-84地球坐标系,采用默卡托投影,可以拓展其他坐标系和投影规则。
- 支持在线和离线两种场景需求,可以自定义在线瓦片地址格式和离线瓦片地址格式。
- 多线程下载和加载瓦片图片文件,多线程绘制,自动缓存瓦片文件。
- 在线模式下,可以开启是否缓存文件,指定缓存路径,将下载的瓦片文件存放到本地,默认优先从缓存文件查找,如果存在缓存文件则加载缓存文件,不存在则联网下载。
- 可以拖动地图,鼠标滚轮放大和缩小地图,以鼠标所在位置作为缩放中心点,提供缩放控件手动单击进行操作。
- 多图层机制,支持多个瓦片叠加图层和图形绘制图层,全部采用双缓冲技术,所有的图形和瓦片全部绘制到一个图片文件上,最终再将图片文件绘制到地图控件。不可见区域的图层包括覆盖物不会触发绘制,降低CPU占用。
- 预加载机制,默认绘制的图层大小以当前区域往四周放大两倍,这样在鼠标拖动和缩放的时候,不会看到明显的加载过程,体验更佳。
- 内置了多种图形覆盖物,包括标注点、折线、多边形、矩形、圆形等,可以设置边框颜色粗细、填充颜色和透明度等参数。
- 标注点支持旋转角度和提示文本,其中提示文本可以设置在标注点的相对位置,位置包括左侧、右侧、上侧、下侧、中间、左上角、右上角、左下角、右下角。标注点本身也可以设置相对位置,默认按照底部居中对齐,一般圆形图标可以设置中心点对齐。标注点图片支持gif动图,可以动态切换静态图和动图。
- 所有的覆盖物可以动态更新前景色、颜色粗细、背景颜色、颜色透明度等。
- 支持删除单个覆盖物、删除一种类型的覆盖物、删除所有覆盖物、隐藏指定覆盖物等。
- 可以动态启动禁用比例尺、十字线、缩放控件、地图拖曳、键盘操作、滚轮缩放、双击放大等特性。
- 可以任意指定经纬度区域进行瓦片拼接保存成图片文件,也可以直接对整个可视区域或者缓存区域的地图图片文件保存。支持任意多边形轮廓保存成图片,比如某个行政区的瓦片保存。
- 覆盖物可以动态设置zindex层叠顺序,值越大,越显示在前面,内部维护着一个zindex表,默认按照添加的先后顺序增加,后面添加的显示在前面,主动设置后,按照设置的zindex来绘制。
- 支持将QWidget对象作为覆盖物添加到地图控件中,跟随地图移动位置,极大提高灵活性,比如可以将自定义控件直接作为地图控件的子对象加入进去。
- 大量使用按需绘制机制,包括内部提供合理的默认值来触发绘制,也可以手动传入参数指定是否需要立即绘制,比如删除了某个覆盖物,有些频繁的操作可以不指定立即绘制,等操作完成后再统一一起绘制,效率更高。
- 默认开启缓存瓦片机制,所有加载过的瓦片文件都存储在内存中,下次再次绘制直接从内存取出来绘制,既不需要从联网获取,也不需要从缓存文件获取,直接内存取出来绘制,响应迅速效率最高体验最佳。
- 支持批量添加覆盖物,比如几万个标注点和圆形,都是瞬间完成绘制,相比web网页的方式,性能提升百倍以上。
- 支持街道图、卫星图、混合图、路网图等各种图层,可以任意叠加N个图层,甚至杂交不同地图厂家的瓦片文件。
- 纯QWidget绘制,非qml也非web,不依赖qml或者浏览器控件,支持极低性能的嵌入式环境。
- 支持任意Qt版本、任意系统、任意编译器,包括嵌入式linux和各种国产电脑环境。