一、前言说明
1、功能概述
航迹规划功能允许用户在地图上通过单击操作逐个添加航线途经点,系统自动生成带有方向指示的连续航迹线,并支持对航线进行动态编辑。主要功能包括:
- 支持在地图上单击添加标注点,点位按添加顺序自动递增编号;
- 自动生成带箭头方向指示的航迹线,清晰展示航行方向;
- 实现航线标注点的拖曳编辑,实时更新航迹与箭头方向;
- 支持删除指定航点或清空全部航点,删除后自动重排序并重绘航线;
- 地图上的标注点与表格数据联动:点击地图点可选中对应表格行,选中表格行时地图点自动高亮;
- 航点支持带序号图标的可视化显示,提升可读性;
- 航线数据支持自动加载与持久化保存,保障用户体验连续性。
2、核心难点与技术实现
2.1 通用化方向箭头绘制
尽管部分主流地图平台(如百度地图、高德地图)提供了原生的箭头线绘制接口,但其样式固定、扩展性差,难以满足多样化业务场景的需求。为此,系统采用基于标注点的通用化箭头实现方案,确保兼容所有支持标注点(Marker)的地图组件。
具体实现思路如下:
- 在每两个相邻航点之间,计算其连线的方位角(即方向角);
- 根据计算出的角度,动态生成一个代表箭头方向的标注点;
- 该箭头标注点使用自定义图标(如SVG箭头图像),并通过设置旋转属性(rotation)与航向保持一致;
- 箭头图标可灵活替换,仅需更换图片资源即可适配不同视觉风格,极大增强了系统的可维护性与一致性。
此方法避免了依赖特定地图厂商的高级绘图接口,实现了“一次开发,多平台适配”的目标。
2.2 航点拖曳编辑与实时更新机制
为提升交互体验,系统支持用户直接在地图上拖动航点以调整航线。此功能的核心挑战在于:拖动一个航点时,需同步更新与其相邻的两条航段及其对应的箭头方向。
实现方案如下:
- 为每个航点标注绑定“拖曳开始”与“拖曳结束”事件监听;
- 拖曳过程中,实时获取当前鼠标位置,更新该航点坐标;
- 动态重新计算受影响的前后两段航迹线的坐标数据,并刷新对应的折线(Polyline)对象;
- 同时重新计算相邻两段航迹的方向角,更新前后两个箭头标注点的旋转角度;
- 拖曳结束后,触发数据持久化,保存最新航线状态。
得益于前期地图组件良好的事件封装机制,各类覆盖物的事件监听可按需绑定与解绑,保证了系统性能与稳定性。
二、效果图
三、代码使用
#include "frmmapdrawmarkerline.h"
#include "ui_frmmapdrawmarkerline.h"
#include "qthelper.h"
#include "maphelper.h"
#include "webview.h"
#include "mapdrawmarkerline.h"frmMapDrawMarkerLine::frmMapDrawMarkerLine(QWidget *parent) : QWidget(parent), ui(new Ui::frmMapDrawMarkerLine)
{ui->setupUi(this);this->initForm();this->initTable();this->loadTable();//mapObj->load();QTimer::singleShot(500, mapObj, SLOT(load()));//QMetaObject::invokeMethod(mapObj, "load", Qt::QueuedConnection);
}frmMapDrawMarkerLine::~frmMapDrawMarkerLine()
{delete ui;
}void frmMapDrawMarkerLine::initForm()
{//设置右侧固定宽度ui->frameRight->setFixedWidth(AppData::RightWidth);flag = "moveMarker";center = "121.56358,31.11566";//实例化浏览器控件并加入到布局webView = new WebView(this);webView->setLayout(ui->gridLayout);connect(webView, SIGNAL(loadSuccess()), this, SLOT(on_btnDrawMarkerLine_clicked()));//实例化地图类MapCore mapCore = (MapCore)AppConfig::MapDrawCore; int zoom = MapHelper::getMapZoom(mapCore, this->objectName());mapObj = MapHelper::getMapObj(this, mapCore);mapObj->setWebView(webView);mapObj->setSaveFile(SaveFile);mapObj->setMapLocal(AppConfig::MapDrawLocal);mapObj->setMapType(AppConfig::MapDrawType);mapObj->setCenterPoint(center);mapObj->setZoom(zoom);//实例化封装类drawMarkerLine = new MapDrawMarkerLine(this);connect(drawMarkerLine, SIGNAL(updatePoints()), this, SLOT(on_btnGetMarkerLine_clicked()));connect(drawMarkerLine, SIGNAL(markerClick(QString)), this, SLOT(markerClick(QString)));connect(drawMarkerLine, SIGNAL(receivePoints(QStringList)), this, SLOT(receivePoints(QStringList)));connect(webView, SIGNAL(receiveDataFromJs(QString, QVariant)), drawMarkerLine, SLOT(receiveDataFromJs(QString, QVariant)));
}void frmMapDrawMarkerLine::initTable()
{QList<QString> columnNames;QList<int> columnWidths;columnNames << "经纬度";columnWidths << 50;int columnCount = columnNames.count();ui->tableWidget->setColumnCount(columnCount);ui->tableWidget->setHorizontalHeaderLabels(columnNames);for (int i = 0; i < columnCount; ++i) {ui->tableWidget->setColumnWidth(i, columnWidths.at(i));}//通用函数设置表格控件QtHelper::initTableView(ui->tableWidget, 25, true);
}void frmMapDrawMarkerLine::loadTable()
{int count = AppConfig::MarkerPoints.count();ui->tableWidget->setRowCount(count);for (int i = 0; i < count; ++i) {QString point = AppConfig::MarkerPoints.at(i);ui->tableWidget->setItem(i, 0, new QTableWidgetItem(point));}
}void frmMapDrawMarkerLine::loadPoint()
{QStringList points;int count = ui->tableWidget->rowCount();for (int i = 0; i < count; ++i) {points << ui->tableWidget->item(i, 0)->text();}//设置默认中心点/没有数据的时候取一个/有的话从坐标点集合取第一个QString center = points.count() == 0 ? "121.56358,31.11566" : points.first();drawMarkerLine->clear();drawMarkerLine->setPara(points);drawMarkerLine->init("test", center, 0);
}void frmMapDrawMarkerLine::runJs(const QString &js)
{mapObj->runJs(js);
}void frmMapDrawMarkerLine::markerClick(const QString &point)
{//先恢复上一个图标QString p = ui->txtPoint->text();if (!p.isEmpty()) {int index = AppConfig::MarkerPoints.lastIndexOf(p);QString flag = QString("marker%1_%0").arg("test").arg(index);QString image = MapHelper::getMarkerIcon("blue", index);this->runJs(QString("setMarker('%1', null, null, null, '%2')").arg(flag).arg(image));}//切换选中图标int index = AppConfig::MarkerPoints.lastIndexOf(point);QString flag = QString("marker%1_%0").arg("test").arg(index);QString image = MapHelper::getMarkerIcon("yellow", index);this->runJs(QString("setMarker('%1', null, null, null, '%2')").arg(flag).arg(image));ui->txtPoint->setText(point);ui->tableWidget->selectRow(index);
}void frmMapDrawMarkerLine::receivePoints(const QStringList &points)
{AppConfig::MarkerPoints = points;AppConfig::writeConfig();this->loadTable();
}void frmMapDrawMarkerLine::on_btnDrawMarkerLine_clicked()
{//开启标注后禁用双击放大if (ui->btnDrawMarkerLine->text() == "开启标注") {mapObj->setEnable(EnableType_DoubleClickZoom, false);drawMarkerLine->setMapObj(mapObj);ui->btnDrawMarkerLine->setText("停止标注");this->loadPoint();} else {mapObj->setEnable(EnableType_DoubleClickZoom, true);drawMarkerLine->stop();drawMarkerLine->setMapObj(NULL);ui->btnDrawMarkerLine->setText("开启标注");}
}void frmMapDrawMarkerLine::on_btnGetMarkerLine_clicked()
{drawMarkerLine->getPoints();int row = ui->tableWidget->currentRow();if (row > 0) {ui->txtPoint->setText(ui->tableWidget->item(row, 0)->text());}
}void frmMapDrawMarkerLine::on_btnDeleteMarkerLine_clicked()
{if (ui->btnDrawMarkerLine->text() == "开启标注") {return;}//移除单个点并重新加载QString point = ui->txtPoint->text().trimmed();if (point.contains(",")) {AppConfig::MarkerPoints.removeOne(point);AppConfig::writeConfig();this->loadTable();this->loadPoint();on_tableWidget_itemSelectionChanged();}
}void frmMapDrawMarkerLine::on_btnClearMarkerLine_clicked()
{if (ui->btnDrawMarkerLine->text() == "开启标注") {return;}if (QtHelper::showMessageBoxQuestion("确定要清空吗? 清空后无法恢复!") == QMessageBox::Yes) {AppConfig::MarkerPoints.clear();AppConfig::writeConfig();this->loadTable();this->loadPoint();}
}void frmMapDrawMarkerLine::on_tableWidget_itemSelectionChanged()
{int row = ui->tableWidget->currentRow();if (row >= 0) {QString point = ui->tableWidget->item(row, 0)->text();this->markerClick(point);}
}void frmMapDrawMarkerLine::on_btnStart_clicked()
{//没有一个数据则不用继续QStringList datas = AppConfig::MarkerPoints;if (datas.count() <= 0) {return;}ui->btnPause->setText("暂停回放");if (ui->btnStart->text() == "开始回放") {QString image = MapHelper::getFlyIcon(mapObj);this->runJs(QString("removeMoveMarker('%1')").arg(flag));this->runJs(QString("addMove('%1', '%2', %3, true, true, '%4', 48, 48)").arg(flag).arg(datas.join("|")).arg(500).arg(image));this->runJs(QString("moveStart('%1')").arg(flag));ui->btnStart->setText("停止回放");ui->widgetBtn->setEnabled(false);} else {this->runJs(QString("moveStop('%1')").arg(flag));ui->btnStart->setText("开始回放");ui->widgetBtn->setEnabled(true);}
}void frmMapDrawMarkerLine::on_btnPause_clicked()
{//启动阶段才能暂停if (ui->btnStart->text() == "开始回放") {return;}if (ui->btnPause->text() == "暂停回放") {this->runJs(QString("movePause('%1')").arg(flag));ui->btnPause->setText("继续回放");} else {this->runJs(QString("moveNext('%1')").arg(flag));ui->btnPause->setText("暂停回放");}
}
四、相关地址
- 国内站点:https://gitee.com/feiyangqingyun
- 国际站点:https://github.com/feiyangqingyun
- 个人作品:https://blog.csdn.net/feiyangqingyun/article/details/97565652
- 文件地址:https://pan.baidu.com/s/1ZxG-oyUKe286LPMPxOrO2A 提取码:o05q 文件名:bin_map.zip
五、功能特点
5.1 地图功能
- 支持多种地图内核,默认采用百度地图,可选高德地图、天地图、腾讯地图、谷歌地图等。
- 同时支持在线地图和离线地图两种模式,离线地图方便在不联网的场景中使用。
- 支持各种地图控件的启用,比如地图导航、地图类型、缩略图、比例尺、全景导航、实时路况、绘图工具、结果面板等。
- 支持多种地图功能的动态启用禁用,比如地图拖曳、键盘操作、滚轮缩放、双击放大、连续缩放、地图测距等。
- 提供众多js函数接口用于交互,参数极其丰富,能够想到的应用场景需求都有。
- 统一的信号槽机制,地图中的结果统一信号发送出去,收到后根据type类型区分。
- 支持地图交互,比如鼠标按下获取对应位置的经纬度。单击标注点弹出对应点的信息。
- 支持添加标注、删除标注、移动标注、清空标注。
- 标注点可以指定图标图片和尺寸,支持gif动图,支持指定以图片中心对齐还是底部中心对齐。可以设置旋转角度,带富文本提示信息。
- 标注点事件支持单击发信号通知和自己弹框显示信息。
- 提供地址转坐标和坐标转地址接口。
- 支持各种图形绘制,包括折线图、多边形、矩形、圆形、弧线等。
- 可显示悬浮的绘图工具栏,直接在地图上划线、标注点、矩形、圆形等。
- 支持各种区域搜索,比如矩形区域、圆形区域,可以按照关键字匹配将搜索结果显示在地图中。
- 可动态添加离线的行政区边界点数据。可以搜索行政区划并获取该区域的边界点数据。数据可以保存到文件以便离线使用。
- 支持点聚合功能,多个小标注点合并到一个大标注点,防止点密集导致交互不友好。
- 可以添加海量点,每个点都可以单击获取对应坐标和信息。
- 所有的覆盖物信息比如标注点、矩形、多边形、折线图等,都可以主动获取对应的信息比如坐标点和路径等。
- 支持路径规划,支持公交路线、自驾路线、步行路线、骑行路线,不同查询支持不同策略,可选最少时间、最少换乘、不走高架等。
- 路径规划结果可以显示在地图中,也可以获取到路径点坐标集合。这个数据可以保存到文件,以便发给机器人或者无人机做导航用来轨迹移动。
- 可以设置不同的地图视图比如街道图、卫星图、混合图。
- 可以设置不同的样式,比如午夜蓝、青草绿等样式风格。
- 可以设置地图的旋转角度和倾斜角度。
- 提供经纬度坐标纠偏转换功能,比如传入的GPS坐标需要转换到百度地图坐标或者高德地图坐标。各种坐标系转换全部离线函数,支持地球坐标系WGS-84、火星坐标系GCJ-02、百度坐标系BD-09之间的互相转换,涵盖了各种地图的坐标系。
- 提供动态轨迹点移动功能,按照给定的经纬度坐标集合平滑移动。
- 同时支持qwidget和qml,支持编译到安卓系统运行。
5.2 其他功能
- 提供离线地图下载模块,可以选择不同的地图内核比如百度地图或者谷歌地图,不同的地图类型比如下载街道图还是卫星图,不同的地图层级,多线程极速下载。
- 表格行实时显示对应的瓦片下载进度,有下载超时时间,重试次数,每个瓦片下载完成都发送信号通知,参数包括下载用时。
- 提供省市轮廓图下载模块,自动下载各个地区的轮廓图,保存到脚本文件或者文本文件。
- 支持手动调整不同区域的轮廓边界,调整后可以主动获取调整后的边界点集合。
- 提供动态点位示例,手动在地图上选点并添加标注,附带自定义的信息比如速度和时间等。
- 提供海量点位示例,批量添加标注点、点聚合、海量点。用于测试环境中支持的最大点位性能。
- 提供动态轨迹示例,在地图上鼠标按下选择起点和终点后,查询路线,获取路径轨迹点,模拟轨迹平滑移动。可以筛选数据将过多的路径点筛选到设定的点数。
- 提供轨迹回放示例,按照指定的轨迹点列表回放,也可以导入轨迹点数据进行回放。同时支持在街道图、卫星图、混合图中回放轨迹。
- 提供省市区域地图示例,采用echart组件,同时支持闪烁点图、迁徙图、区域地图、世界地图、仪表盘等。可以设置标题、提示信息、背景颜色、文字颜色、线条颜色、区域颜色等各种颜色。
- 省市区域地图示例,内置世界地图、全国地图、省份地图、地区地图,可以精确到县,所有地图全部离线使用。可设置城市的名称、值、经纬度集合。
- 内置通用浏览器组件,同时支持webkit/webengine/miniblink等内核。提供网页控件示例,演示打开网页和本地网页文件。
- 支持任意Qt版本、任意系统、任意编译器。