一、前言说明
在2026年的今天,能够不用AI,纯古法编程实现这个航线规划,还是有不小难度的,在地图组件的基础上开发,之前已经实现了超级标注点图形,加上折线图形的绘制,以及支持编辑状态拖曳调整,对于航线规划的基础功能都有了,现在就是还需要在折线线条上绘制一个箭头。以前web版本的航线规划,用了个取巧的方案,就是搞了个标注点显示在中间位置,对于箭头作为标注点的图片,然后标注点可以旋转,每次都计算上一个点和当前点之间的旋转角度,尽管widget开发的地图组件也可以按照这个思路去完成,想了很久还是决定直接用painter绘制更优,没必要再引人一个多余的标注点,毕竟一整个航线下来,标注点的数量成倍增加了,如果能直接在折线上根据两点坐标,计算出来并绘制箭头,好处实在太多了,其实那个箭头就是传入两个点,计算出中间点,然后两侧伸展两个点,最后三个点形成一个新的折线,绘制这个折线即可。
web版本的地图组件本身支持的最大标注点数量有限,测下来500个就已经是性能瓶颈了,明显的卡顿不及时,而widget版本的至少1W个起步都毫无压力,而且是瞬间完成,性能百倍提升一点不夸张。本人之前搞个自定义控件大全,在圈内有Qt控件祖师爷之称,所谓心中有坐标,万物皆painter。就是因为有这个擅长点,各种各样的绘制都不在话下。
二、效果图
三、相关代码
#include "markermove.h"
#include "maputil.h"
#include "overlayhelper.h"
MarkerMove::MarkerMove(MapWidget *mapWidget, const QString &flag, const QPixmap &pixmap, const QVector<QPointF> &points, int speed, int interval, bool smooth, bool moveInCenter) : QObject(mapWidget)
{
//生成对应覆盖物的唯一标识
this->dataLineFlag = "dataLine_" + flag;
this->moveLineFlag = "moveLine_" + flag;
this->markerMoveFlag = "markerMove_" + flag;
this->mapWidget = mapWidget;
this->pixmap = pixmap;
this->speed = speed;
this->smooth = smooth;
this->moveInCenter = moveInCenter;
//约定第一个点是起始点/如果只有一个点则说明是动态添加数据生成轨迹/多个点则是历史轨迹回放
angle = 0;
home = QPointF(121.424362, 31.175942);
int count = points.count();
if (count > 0) {
home = points.first();
}
//多个数据则说明是轨迹回放/设置到可视区域/实时轨迹则设置起点作为中心点
if (count > 1) {
mapWidget->setAutoView(points);
angle = MapUtil::getAngle(points.at(0), points.at(1));
//传入速度也就是每次移动的距离/逐个取出点生成平滑的点
if (smooth) {
for (int i = 0; i < count - 1; ++i) {
this->points << MapUtil::getLinePoints(points.at(i), points.at(i + 1), speed);
}
} else {
this->points = points;
}
} else {
this->points = points;
mapWidget->setCenter(home);
}
//添加数据轨迹线
mapWidget->addPolyline(dataLineFlag, points, QColor(255, 0, 0), 5);
//添加移动轨迹线
mapWidget->addPolyline(moveLineFlag, QVector<QPointF>() << home, QColor(0, 0, 0), 3);
//添加移动标注点
marker = mapWidget->addMarker(markerMoveFlag, home, QString(), pixmap, angle, 2);
//定时器移动标注点
timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(move()));
timer->setInterval(interval);
}
MarkerMove::~MarkerMove()
{
}
void MarkerMove::start()
{
index = 0;
if (points.count() > 1) {
angle = MapUtil::getAngle(points.at(0), points.at(1));
}
timer->start();
this->move();
emit moveStep(home);
}
void MarkerMove::pause()
{
timer->stop();
}
void MarkerMove::play()
{
timer->start();
}
void MarkerMove::stop()
{
timer->stop();
this->reset();
}
void MarkerMove::clear()
{
//停止定时器
timer->stop();
this->points.clear();
//删除轨迹线和标注点
OverlayHelper::redraw = false;
mapWidget->deleteOverlay(dataLineFlag);
mapWidget->deleteOverlay(moveLineFlag);
mapWidget->deleteOverlay(markerMoveFlag);
OverlayHelper::redraw = true;
}
void MarkerMove::append(const QPointF &point)
{
QPointF curPoint(point);
QPointF prePoint = points.last();
if (smooth) {
this->points << MapUtil::getLinePoints(prePoint, curPoint, speed);
} else {
this->points << curPoint;
}
//添加到数据轨迹
mapWidget->addPolylineData(dataLineFlag, curPoint);
}
void MarkerMove::setInterval(int interval)
{
timer->setInterval(interval);
}
void MarkerMove::setSmooth(bool smooth)
{
this->smooth = smooth;
}
void MarkerMove::setMoveInCenter(bool moveInCenter)
{
this->moveInCenter = moveInCenter;
}
void MarkerMove::setDataLineVisible(bool visible)
{
mapWidget->setOverlayVisible(dataLineFlag, visible);
}
void MarkerMove::setMoveLineVisible(bool visible)
{
mapWidget->setOverlayVisible(moveLineFlag, visible);
}
void MarkerMove::setDataLineColor(const QColor &color, int width)
{
mapWidget->updatePolyline(dataLineFlag, QVector<QPointF>(), color, width);
}
void MarkerMove::setMoveLineColor(const QColor &color, int width)
{
mapWidget->updatePolyline(moveLineFlag, QVector<QPointF>(), color, width);
}
void MarkerMove::setText(const QString &text)
{
OverlayHelper::updateText(marker, text);
}
void MarkerMove::setTextPara(const QColor &textColor, int textAlign, int textOffset, const QString &fontName, int fontSize, const QColor &bgColor, int bgAlpha, const QColor &borderColor, int borderWidth)
{
OverlayHelper::updateTextPara(marker, textColor, textAlign, textOffset, fontName, fontSize, bgColor, bgAlpha, borderColor, borderWidth);
}
void MarkerMove::move()
{
//每次索引递增
index++;
//到了末尾则重来
if (index >= points.count()) {
this->reset();
return;
}
//过滤重复的坐标
QPointF curPoint = points.at(index);
QPointF prePoint = points.at(index - 1);
if (prePoint == curPoint) {
this->move();
return;
}
//移动到新的位置/带旋转角度
int angle = MapUtil::getAngle(prePoint, curPoint);
mapWidget->updateMarker(markerMoveFlag, curPoint, "", QPixmap(), angle);
mapWidget->addPolylineData(moveLineFlag, curPoint);
emit moveStep(curPoint);
//最新点一直作为中心点
if (moveInCenter) {
mapWidget->setCenter(prePoint);
}
}
void MarkerMove::reset()
{
//复位索引
index = 0;
//移动到起始点
mapWidget->updateMarker(markerMoveFlag, home, QString(), QPixmap(), angle, 2);
//移动轨迹复位
mapWidget->setPolylineData(moveLineFlag, QVector<QPointF>() << home);
//移到初始位置
emit moveStep(home);
四、相关地址
- 国内站点: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对象作为覆盖物添加到地图控件中,跟随地图移动位置,极大提高灵活性,比如可以将自定义控件直接作为地图控件的子对象加入进去。
- 内置MarkerMove轨迹移动类,支持历史轨迹数据回放和实时轨迹移动,可设置图标、轨迹线的颜色和粗细、移动速度、移动间隔、平滑移动等,支持多条轨迹线条同时移动。
- 内置MarkerLine航迹规划类,支持动态添加航迹点,显示对应箭头,可以动态拖曳调整航迹点的位置,选中点高亮显示。
- 大量使用按需绘制机制,包括内部提供合理的默认值来触发绘制,也可以手动传入参数指定是否需要立即绘制,比如删除了某个覆盖物,有些频繁的操作可以不指定立即绘制,等操作完成后再统一一起绘制,效率更高。
- 默认开启缓存瓦片机制,所有加载过的瓦片文件都存储在内存中,下次再次绘制直接从内存取出来绘制,既不需要从联网获取,也不需要从缓存文件获取,直接内存取出来绘制,响应迅速效率最高体验最佳。
- 支持批量添加覆盖物,比如几万个标注点和圆形,都是瞬间完成绘制,相比web网页的方式,性能提升百倍以上。
- 支持街道图、卫星图、混合图、路网图等各种图层,可以任意叠加N个图层,甚至杂交不同地图厂家的瓦片文件。
- 纯QWidget绘制,非qml也非web,不依赖qml或者浏览器控件,支持极低性能的嵌入式环境。
- 原创轻量级,5000行代码,架构漂亮,注释详细,拓展方便,容易学习,适合各种初学者和进阶者,方便二次开发。
- 支持任意Qt版本、任意系统、任意编译器,包括嵌入式linux和各种国产电脑环境。古法编程,不含任何AI代码,品质保证。