一、前言说明
1、功能概述
航迹规划功能允许用户在地图上通过单击操作逐个添加航线途经点,系统自动生成带有方向指示的连续航迹线,并支持对航线进行动态编辑。主要功能包括:
- 支持在地图上单击添加标注点,点位按添加顺序自动递增编号;
- 自动生成带箭头方向指示的航迹线,清晰展示航行方向;
- 实现航线标注点的拖曳编辑,实时更新航迹与箭头方向;
- 支持删除指定航点或清空全部航点,删除后自动重排序并重绘航线;
- 地图上的标注点与表格数据联动:点击地图点可选中对应表格行,选中表格行时地图点自动高亮;
- 航点支持带序号图标的可视化显示,提升可读性;
- 航线数据支持自动加载与持久化保存,保障用户体验连续性。
2、核心难点与技术实现
2.1 通用化方向箭头绘制
尽管部分主流地图平台(如百度地图、高德地图)提供了原生的箭头线绘制接口,但其样式固定、扩展性差,难以满足多样化业务场景的需求。为此,系统采用基于标注点的通用化箭头实现方案,确保兼容所有支持标注点(Marker)的地图组件。
具体实现思路如下:
- 在每两个相邻航点之间,计算其连线的方位角(即方向角);
- 根据计算出的角度,动态生成一个代表箭头方向的标注点;
- 该箭头标注点使用自定义图标(如SVG箭头图像),并通过设置旋转属性(rotation)与航向保持一致;
- 箭头图标可灵活替换,仅需更换图片资源即可适配不同视觉风格,极大增强了系统的可维护性与一致性。
此方法避免了依赖特定地图厂商的高级绘图接口,实现了"一次开发,多平台适配"的目标。
2.2 航点拖曳编辑与实时更新机制
为提升交互体验,系统支持用户直接在地图上拖动航点以调整航线。此功能的核心挑战在于:拖动一个航点时,需同步更新与其相邻的两条航段及其对应的箭头方向。
实现方案如下:
- 为每个航点标注绑定"拖曳开始"与"拖曳结束"事件监听;
- 拖曳过程中,实时获取当前鼠标位置,更新该航点坐标;
- 动态重新计算受影响的前后两段航迹线的坐标数据,并刷新对应的折线(Polyline)对象;
- 同时重新计算相邻两段航迹的方向角,更新前后两个箭头标注点的旋转角度;
- 拖曳结束后,触发数据持久化,保存最新航线状态。
得益于前期地图组件良好的事件封装机制,各类覆盖物的事件监听可按需绑定与解绑,保证了系统性能与稳定性。
二、效果图

三、代码使用
cpp
#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版本、任意系统、任意编译器。