一、前言
实时动态轨迹经历过很多个版本的迭代,此功能最初是一个客户定制的,主要是需要在地图上动态显示GPS的运动轨迹,有个应用场景就是一个带有监控的车子,实时在运动中,后台可以接收到经纬度信息,需要绘制对应的轨迹,相当于这些摄像机点位是动态移动的,这样就可以观测到摄像机的实时位置信息,双击摄像机还可以弹出画面实时预览,很直观。
GPS运动轨迹这个功能,也需要用到js的知识,其实就是封装一个js函数,绘制对应的线条路径,这个轨迹点可能包括的信息有经度、纬度、速度、时间、是否标记、时间等信息,写个结构体封装下,方便后期拓展,是否标记的含义是是否改点同时作为一个设备点添加,分段线的含义。
后面陆续增加了可以设置旋转角度、可以过滤坐标点这两个要点,设置旋转角度采用的是内置的setRotation函数,流程是先从一堆覆盖物中通过唯一标识比如name找到当前要移动的点,然后对这个标注点调用setRotation设置要旋转的角度值,所以这里衍生了另外一个需求,如何计算两个点之间的旋转角度值,这个值必须是提前计算好的,这就要用到数学知识了,用atan2来计算,同时做矫正。
二、功能特点
- 定时器排队下载省市轮廓图点坐标集合存储到JS文件。
- 支持一个行政区域多个不规则区域下载。
- 自动计算行政区域的下载轮廓数量。
- 可精确选择省份、市区、县城,也可直接输入行政区域的名称。
- 可以设置下载间隔、随时开始下载和停止下载。
- 提供编辑边界功能,可以直接在地图上编辑好不规则区域的点集合,然后获取边界点集合数据,这个可以用来自己绘制区域拿到数据,比如某个乡镇甚至某个小区的行政区域数据,很牛逼。
三、体验地址
- 体验地址:https://pan.baidu.com/s/1ZxG-oyUKe286LPMPxOrO2A 提取码:o05q 文件名:bin_map.zip
- 国内站点:https://gitee.com/feiyangqingyun
- 国际站点:https://github.com/feiyangqingyun
- 个人主页:https://blog.csdn.net/feiyangqingyun
- 知乎主页:https://www.zhihu.com/people/feiyangqingyun/
四、效果图
五、相关代码
void frmMapGps::receiveDataFromJs(const QString &type, const QVariant &data)
{
if (data.isNull()) {
return;
}
//qDebug() << "frmMapGps" << type << data;
QString result = data.toString();
if (type == "point") {
if (ui->ckSelectAddr->isChecked()) {
//判断哪里勾选了就设置到哪里
QString point = WebHelper::getLngLat2(result);
//判断哪里勾选了就设置到哪里
if (ui->rbtnStartAddr->isChecked()) {
ui->txtStartAddr->setText(point);
} else {
ui->txtEndAddr->setText(point);
}
}
} else if (type == "routepoints") {
//将查询路径转换成经纬度坐标点集合数据显示
routeDatas.clear();
ui->tableWidgetSource->clearContents();
//可能会有多个路径集合,目前测试下来都是一个路径集合
QStringList datas = result.split("|");
foreach (QString data, datas) {
QStringList points = data.split(";");
routeDatas << points;
int count = points.count();
ui->tableWidgetSource->setRowCount(count);
for (int i = 0; i < count; ++i) {
addItem(ui->tableWidgetSource, i, points.at(i));
}
}
setInfo(0, 0, 0);
}
}
void frmMapGps::runJs(const QString &js)
{
web->runJs(js);
}
void frmMapGps::on_btnSearchData_clicked()
{
QString startAddr = ui->txtStartAddr->text().trimmed();
QString endAddr = ui->txtEndAddr->text().trimmed();
baidu->setRotueInfo(2, 0, startAddr, endAddr);
this->loadMap();
}
void frmMapGps::moveMarker()
{
QTableWidget *tableWidget = getTableWidget();
int row = tableWidget->currentRow();
int count = tableWidget->rowCount();
if (row >= 0 && row < count) {
//找出和上一个点之间的角度
int angle = 0;
QString point = tableWidget->item(row, 1)->data(Qt::UserRole).toString();
//第一个点和最后一个点不用处理
if (row > 0 && row < count - 1) {
//上一个点坐标
QString point2 = tableWidget->item(row - 1, 1)->data(Qt::UserRole).toString();
//计算当前上一个点和当前点的旋转角度
angle = WebHelper::getAngle(point2, point);
}
//执行移动设备点函数,参数带旋转角度
QString js = QString("moveMarker('%1', '%2', %3)").arg(name).arg(point).arg(angle);
runJs(js);
//重新绘制轨迹点
if (ui->cboxMoveMode->currentIndex() == 0) {
//清空之前的轨迹点
js = QString("deleteOverlay('Polyline')");
runJs(js);
//取出第一个点到当前焦点所在行的点组成已经走过的轨迹点集合重新绘制
QStringList points;
for (int i = 0; i <= row; ++i) {
points << tableWidget->item(i, 1)->data(Qt::UserRole).toString();
}
js = QString("addPolyline('%1')").arg(points.join("|"));
runJs(js);
}
//显示当前第几个数据
setInfo(angle, row + 1, count);
tableWidget->setCurrentCell(row + 1, 0);
} else {
on_btnTestData_clicked();
}
}
void frmMapGps::on_btnTestData_clicked()
{
QTableWidget *tableWidget = getTableWidget();
if (ui->btnTestData->text() == "模拟轨迹") {
//限制最小数量
if (tableWidget->rowCount() < 2) {
return;
}
//第一步: 添加一个标记
name = ui->txtDeviceName->text().trimmed();
if (name.isEmpty()) {
name = "马航MH370";
}
//图片文件在可执行文件下的config/device目录
QString icon = "./device/device_airplane.png";
int size = 60;
QString js = QString("addMarker('%1', '', '', '', 60, '%1', 0, 0, '%2', %3)").arg(name).arg(icon).arg(size);
runJs(js);
//第二步: 移到第一个点
tableWidget->setFocus();
tableWidget->setCurrentCell(0, 0);
ui->btnTestData->setText("停止模拟");
ui->tabWidget->setTabEnabled(ui->tableWidgetSource->isVisible() ? 1 : 0, false);
//第三步: 启动定时器并立即执行一次
int index = ui->cboxMoveInterval->currentIndex();
timer->start(ui->cboxMoveInterval->itemData(index).toInt());
moveMarker();
} else {
//清空标记
QString js = QString("deleteMarker('%1')").arg(name);
runJs(js);
//停止定时器
timer->stop();
ui->btnTestData->setText("模拟轨迹");
ui->tabWidget->setTabEnabled(ui->tableWidgetSource->isVisible() ? 1 : 0, true);
}
}
void frmMapGps::on_btnCheckData_clicked()
{
if (timer->isActive()) {
return;
}
//第一步: 计算总数,求平均值=实际总数/预期总数+1,预期总数>=实际总数则不用处理
int countSource = ui->tableWidgetSource->rowCount();
int countTarget = ui->txtPointCount->text().trimmed().toInt();
if (countTarget >= countSource) {
QUIHelper::showMessageBoxError("目标点数不能大于等于原数据点数!");
ui->txtPointCount->setFocus();
return;
}
//第二步: 根据平均值挨个取出值
QStringList points;
int avg = countSource / countTarget + 1;
for (int i = 0; i < countSource; i += avg) {
QString point = ui->tableWidgetSource->item(i, 1)->data(Qt::UserRole).toString();
points << point;
}
//必须加上末尾这个作为结束,如果刚好除尽则不用
QString point = ui->tableWidgetSource->item(countSource - 1, 1)->data(Qt::UserRole).toString();
if (points.last() != point) {
points << point;
}
//第三步: 将数据重新填入筛选数据列表
int count = points.count();
ui->tableWidgetTarget->clearContents();
ui->tableWidgetTarget->setRowCount(count);
for (int i = 0; i < count; ++i) {
addItem(ui->tableWidgetTarget, i, points.at(i));
}
ui->tabWidget->setCurrentIndex(1);
}
void frmMapGps::on_btnDrawData_clicked()
{
if (routeDatas.count() == 0) {
QUIHelper::showMessageBoxError("请先单击查询路线获取路线的坐标点集合!");
return;
}
//清空之前的轨迹点
runJs("deleteOverlay('Polyline')");
//将收到的路径点集合分线段绘制
foreach (QStringList data, routeDatas) {
QString points = data.join("|");
QString js = QString("addPolyline('%1', '#ff0000')").arg(points);
runJs(js);
}
}