一、前言说明

在地图应用中,有很多时候是需要断网环境中离线使用的,一般会采用两种做法,一种是只下载好离线瓦片地图,然后根据不同的缩放和经纬度坐标绘制瓦片。这种方式优点是任何地图都支持,只需要拿到瓦片即可,缺点是其他的接口都需要自己处理,比如覆盖物的绘制,工作量巨大,尽管Qt的qml部分提供了location模块用于这个处理,但是功能还不是很完善,而且不支持widget,对于只会widget的我来说,歇菜。现阶段采用方式二,也就是浏览器控件加载地图的js交互来实现,优点就不说了,超过优点,缺点就一个,必须依赖浏览器控件,资源占用大。

百度地图的离线js开发包,网上到处都是,大部分都是2.0版本,这个基本上功能是齐全的,直接用就行,至于天地图,网上几乎没有,本人废了九牛二虎之力才搞定,一点点从官网趴下来的,所有支持的离线功能全部存在,非常完美,关键是掌握了这个方法思路就很好办,无论后期版本升级到多少,都可以轻轻松松实现最新版本的离线地图js开发包。离线搞定后,手机上运行离线地图就非常容易了,使用qml的浏览器控件加载离线网页即可。在手机上只有qml的浏览器组件能使用,widget的不支持,因为qml的浏览器组件使用本地浏览器内核,而不是webkit或者webengine,手机端是什么底层浏览器就是用何种浏览器。

二、相关代码

#include "frmmapqml.h"
#include "ui_frmmapqml.h"
#include "qthelper.h"
#include "maphelper.h"
#include "mapwebsocket.h" //安卓上如果提示 net::ERR_CLEARTEXT_NOT_PERMITTED 需要在AndroidManifest.xml文件的Application节点添加 android:usesCleartextTraffic="true"
frmMapQml::frmMapQml(QWidget *parent) : QWidget(parent), ui(new Ui::frmMapQml)
{
ui->setupUi(this);
this->initForm();
this->initConfig();
} frmMapQml::~frmMapQml()
{
delete ui;
} void frmMapQml::showEvent(QShowEvent *)
{
//首次显示的时候自动加载/下面这种写法表示异步记载
static bool isLoad = false;
if (!isLoad) {
isLoad = true;
QMetaObject::invokeMethod(this, "loadMap", Qt::QueuedConnection);
}
} void frmMapQml::initForm()
{
mapObj = NULL;
flag = "movePerson"; //拿到qml对象
qmlObj = ui->quickWidget->rootObject(); //实例化websocket通信用于网页交互
connect(MapWebSocket::Instance(), SIGNAL(receiveDataFromJs(QString, QString)), this, SLOT(receiveDataFromJs(QString, QString)));
MapWebSocket::Instance()->listen();
} void frmMapQml::initConfig()
{
MapHelper::loadMapCore(ui->cboxMapCore, AppConfig::MapCore);
connect(ui->cboxMapCore, SIGNAL(currentIndexChanged(int)), this, SLOT(saveConfig()));
connect(ui->cboxMapCore, SIGNAL(currentIndexChanged(int)), this, SLOT(loadMap())); ui->cboxMoveSpeed->setCurrentIndex(ui->cboxMoveSpeed->findText(QString::number(AppConfig::MoveSpeed)));
connect(ui->cboxMoveSpeed, SIGNAL(currentIndexChanged(int)), this, SLOT(saveConfig()));
connect(ui->cboxMoveSpeed, SIGNAL(currentIndexChanged(int)), this, SLOT(loadData())); ui->txtStartAddr->setText(AppConfig::StartAddr);
connect(ui->txtStartAddr, SIGNAL(textChanged(QString)), this, SLOT(saveConfig())); ui->txtEndAddr->setText(AppConfig::EndAddr);
connect(ui->txtEndAddr, SIGNAL(textChanged(QString)), this, SLOT(saveConfig()));
} void frmMapQml::saveConfig()
{
AppConfig::MapCore = ui->cboxMapCore->itemData(ui->cboxMapCore->currentIndex()).toInt();
AppConfig::MoveSpeed = ui->cboxMoveSpeed->currentText().toInt();
AppConfig::StartAddr = ui->txtStartAddr->text().trimmed();
AppConfig::EndAddr = ui->txtEndAddr->text().trimmed();
AppConfig::writeConfig();
} void frmMapQml::loadMap()
{
//根据不同地图内核实例化地图类
MapCore mapCore = (MapCore)ui->cboxMapCore->itemData(ui->cboxMapCore->currentIndex()).toInt();
MapHelper::initMapObj(this, &mapObj, mapCore);
mapObj->setMapType(0);
mapObj->setMapLocal(false);
QString html = mapObj->load(); //将生成的地图网页文件加载到qml中
QString file = "file:///" + mapObj->getFileName();
//安卓上放在固定的目录
#ifdef Q_OS_ANDROID
file = QString("file:///android_asset/%1/map.html").arg(MapHelper::getMapPath(mapCOre));
#endif QMetaObject::invokeMethod((QObject *)qmlObj, "load", Q_ARG(QVariant, file));
//QMetaObject::invokeMethod((QObject *)qmlObj, "loadHtml", Q_ARG(QVariant, html)); ui->txtResult->clear();
} void frmMapQml::loadData()
{
if (datas.count() <= 0) {
return;
} //生成路径轨迹
QString points = datas.join(";");
this->runJs(QString("clearOverlay()"));
this->runJs(QString("drawRoute('%1', '#01caf4', 8, 1.0, '#ffffff')").arg(points)); //生成移动对象
points = datas.join("|");
int speed = ui->cboxMoveSpeed->currentText().toInt();
QString image = "../mapimage/move_fly.png";
if (mapObj->getMapCore() == MapCore_GaoDe && mapObj->getVersionKey().startsWith("2.")) {
image = "../mapimage/move_fly2.png";
} this->runJs(QString("addMove('%1', '%2', %3, true, false, '%4', 48, 48)").arg(flag).arg(points).arg(speed).arg(image));
} void frmMapQml::runJs(const QString &js)
{
QMetaObject::invokeMethod((QObject *)qmlObj, "runJs", Q_ARG(QVariant, js));
} void frmMapQml::receiveDataFromJs(const QString &type, const QString &result)
{
if (type == "click") {
QString info = QString("触发鼠标单击\n当前经纬度值: %1").arg(result);
QtHelper::showMessageBoxInfo(info);
} else if (type == "rightclick") {
QString info = QString("触发鼠标右键\n当前经纬度值: %1").arg(result);
QtHelper::showMessageBoxInfo(info);
} else if (type == "dblclick") {
QString info = QString("触发鼠标双击\n当前经纬度值: %1").arg(result);
QtHelper::showMessageBoxInfo(info);
} else if (type == "geocoderresult") {
QStringList list = result.split("|");
QString flag = list.first();
if (flag == "startAddr") {
startPoint = list.last();
QString endAddr = ui->txtEndAddr->text().trimmed();
this->runJs(QString("getPointByAddr('endAddr', '%1')").arg(endAddr));
} else if (flag == "endAddr") {
endPoint = list.last();
} //两个地址都有了再开启路径规划
if (!startPoint.isEmpty() && !endPoint.isEmpty()) {
this->runJs(QString("searchRoute(2, '%1', '%2', '%3')").arg(0).arg(startPoint).arg(endPoint));
}
} else if (type == "routeresult") {
QStringList list = result.split("|");
QString result = MapHelper::getRouteResult(list.first().toInt(), list.last().toInt());
ui->txtResult->setText(result);
} else if (type == "routepoints") {
datas.clear();
QStringList list = result.split("|");
foreach (QString data, list) {
datas << data.split(";");
} //有些地图内核需要延迟一点载入数据
MapCore mapCore = mapObj->getMapCore();
int interval = (mapCore == MapCore_BaiDuGL ? 500 : 0);
QTimer::singleShot(interval, this, SLOT(loadData()));
} else if (type == "movestep") {
MapCore mapCore = mapObj->getMapCore();
if (mapCore != MapCore_BaiDuGL && mapCore != MapCore_GaoDe) {
QString point = result.split("|").last();
this->runJs(QString("setCenter('%1')").arg(point));
}
} else if (type == "moveend") {
ui->widgetPara->setEnabled(true);
on_btnStart_clicked();
//再次执行则表示循环
QMetaObject::invokeMethod(this, "on_btnStart_clicked", Qt::QueuedConnection);
}
} void frmMapQml::on_btnSelect_clicked()
{
//执行地址转经纬度操作/路径规划一般只支持经纬度参数
startPoint = endPoint = "";
QString startAddr = ui->txtStartAddr->text().trimmed();
this->runJs(QString("getPointByAddr('startAddr', '%1')").arg(startAddr));
} void frmMapQml::on_btnStart_clicked()
{
if (datas.count() <= 0) {
QtHelper::showMessageBoxError("请先单击查询路线获取路线的坐标点集合!");
return;
} if (ui->btnStart->text() == "开始导航") {
this->runJs(QString("addLine('%1', '%2', '#ff0000', 6)").arg(flag).arg(datas.first()));
//this->runJs(QString("setTilt(%1)").arg(60));
this->runJs(QString("moveStart('%1')").arg(flag));
ui->btnStart->setText("停止导航");
ui->widgetPara->setEnabled(false);
} else {
this->runJs(QString("moveStop('%1')").arg(flag));
ui->btnStart->setText("开始导航");
ui->widgetPara->setEnabled(true);
}
}

三、相关链接

  1. 体验地址:https://pan.baidu.com/s/1ZxG-oyUKe286LPMPxOrO2A 提取码:o05q 文件名:bin_map.zip
  2. 国内站点:https://gitee.com/feiyangqingyun
  3. 国际站点:https://github.com/feiyangqingyun

四、效果图

五、功能特点

5.1 地图功能

  1. 支持多种地图内核,默认采用百度地图,可选高德地图、天地图、腾讯地图、谷歌地图等。
  2. 同时支持在线地图和离线地图两种模式,离线地图方便在不联网的场景中使用。
  3. 支持各种地图控件的启用,比如地图导航、地图类型、缩略图、比例尺、全景导航、实时路况、绘图工具、结果面板等。
  4. 支持多种地图功能的动态启用禁用,比如地图拖曳、键盘操作、滚轮缩放、双击放大、连续缩放、地图测距等。
  5. 提供众多js函数接口用于交互,参数极其丰富,能够想到的应用场景需求都有。
  6. 统一的信号槽机制,地图中的结果统一信号发送出去,收到后根据type类型区分。
  7. 支持地图交互,比如鼠标按下获取对应位置的经纬度。单击标注点弹出对应点的信息。
  8. 支持添加标注、删除标注、移动标注、清空标注。
  9. 标注点可以指定图标图片和尺寸,支持gif动图,支持指定以图片中心对齐还是底部中心对齐。可以设置旋转角度,带富文本提示信息。
  10. 标注点事件支持单击发信号通知和自己弹框显示信息。
  11. 提供地址转坐标和坐标转地址接口。
  12. 支持各种图形绘制,包括折线图、多边形、矩形、圆形、弧线等。
  13. 可显示悬浮的绘图工具栏,直接在地图上划线、标注点、矩形、圆形等。
  14. 支持各种区域搜索,比如矩形区域、圆形区域,可以按照关键字匹配将搜索结果显示在地图中。
  15. 可动态添加离线的行政区边界点数据。可以搜索行政区划并获取该区域的边界点数据。数据可以保存到文件以便离线使用。
  16. 支持点聚合功能,多个小标注点合并到一个大标注点,防止点密集导致交互不友好。
  17. 可以添加海量点,每个点都可以单击获取对应坐标和信息。
  18. 所有的覆盖物信息比如标注点、矩形、多边形、折线图等,都可以主动获取对应的信息比如坐标点和路径等。
  19. 支持路径规划,支持公交路线、自驾路线、步行路线、骑行路线,不同查询支持不同策略,可选最少时间、最少换乘、不走高架等。
  20. 路径规划结果可以显示在地图中,也可以获取到路径点坐标集合。这个数据可以保存到文件,以便发给机器人或者无人机做导航用来轨迹移动。
  21. 可以设置不同的地图视图比如街道图、卫星图、混合图。
  22. 可以设置不同的样式,比如午夜蓝、青草绿等样式风格。
  23. 可以设置地图的旋转角度和倾斜角度。
  24. 提供经纬度坐标纠偏转换功能,比如传入的GPS坐标需要转换到百度地图坐标或者高德地图坐标。各种坐标系转换全部离线函数,支持地球坐标系WGS-84、火星坐标系GCJ-02、百度坐标系BD-09之间的互相转换,涵盖了各种地图的坐标系。
  25. 提供动态轨迹点移动功能,按照给定的经纬度坐标集合平滑移动。
  26. 同时支持qwidget和qml,支持编译到安卓系统运行。

5.2 其他功能

  1. 提供离线地图下载模块,可以选择不同的地图内核比如百度地图或者谷歌地图,不同的地图类型比如下载街道图还是卫星图,不同的地图层级,多线程极速下载。
  2. 表格行实时显示对应的瓦片下载进度,有下载超时时间,重试次数,每个瓦片下载完成都发送信号通知,参数包括下载用时。
  3. 提供省市轮廓图下载模块,自动下载各个地区的轮廓图,保存到脚本文件或者文本文件。
  4. 支持手动调整不同区域的轮廓边界,调整后可以主动获取调整后的边界点集合。
  5. 提供动态点位示例,手动在地图上选点并添加标注,附带自定义的信息比如速度和时间等。
  6. 提供海量点位示例,批量添加标注点、点聚合、海量点。用于测试环境中支持的最大点位性能。
  7. 提供动态轨迹示例,在地图上鼠标按下选择起点和终点后,查询路线,获取路径轨迹点,模拟轨迹平滑移动。可以筛选数据将过多的路径点筛选到设定的点数。
  8. 提供轨迹回放示例,按照指定的轨迹点列表回放,也可以导入轨迹点数据进行回放。同时支持在街道图、卫星图、混合图中回放轨迹。
  9. 提供省市区域地图示例,采用echart组件,同时支持闪烁点图、迁徙图、区域地图、世界地图、仪表盘等。可以设置标题、提示信息、背景颜色、文字颜色、线条颜色、区域颜色等各种颜色。
  10. 省市区域地图示例,内置世界地图、全国地图、省份地图、地区地图,可以精确到县,所有地图全部离线使用。可设置城市的名称、值、经纬度集合。
  11. 内置通用浏览器组件,同时支持webkit/webengine/miniblink等内核。提供网页控件示例,演示打开网页和本地网页文件。
  12. 支持任意Qt版本、任意系统、任意编译器。

Qt/C++离线地图的加载和交互/可以离线使用/百度和天地图离线/支持手机上运行的更多相关文章

  1. Qt中如何 编写插件 加载插件 卸载插件

    Qt中如何 编写插件 加载插件 卸载插件是本文要介绍的内容.Qt提供了一个类QPluginLoader来加载静态库和动态库,在Qt中,Qt把动态库和静态库都看成是一个插件,使用QPluginLoade ...

  2. vue10行代码实现上拉翻页加载更多数据,纯手写js实现下拉刷新上拉翻页不引用任何第三方插件

    vue10行代码实现上拉翻页加载更多数据,纯手写js实现下拉刷新上拉翻页不引用任何第三方插件/库 一提到移动端的下拉刷新上拉翻页,你可能就会想到iScroll插件,没错iScroll是一个高性能,资源 ...

  3. npm报错:无法加载文件 D:\nodejs\node_global\webpack.ps1,因为在此系统上禁止运行脚本

    npm报错 在 windows终端输入 vue init webpack app, 创建一个名为 app 的 Vue 项目时报错如下: 无法加载文件 D:\nodejs\node_global\web ...

  4. 运行npm报错:无法加载文件 D:\nodejs\node_global\webpack.ps1,因为在此系统上禁止运行脚本

    npm报错 在 windows终端输入 vue init webpack app, 创建一个名为 app 的 Vue 项目时报错如下: 无法加载文件 D:\nodejs\node_global\web ...

  5. IClient for js开发之地图的加载

    进行web开发之前首先需要安装IServer以及iClient for JavaScript的开发包.在这两中都具备的前提下进行第一步,如何调用IServer中发布的服务 调用iServer 中发布的 ...

  6. Android 百度地图开发问题----解决地图有时候加载不出来问题

    相信很多人在开发百度地图的时候会出现百度地图有时候会加载不出来,只显示网格图. 这个问题究其原因就是申请百度key的时候填写的SHA1也就是指纹证书有问题.估计很多开发者都是照着百度开放平台上介绍的流 ...

  7. QT自定义控件系列(二) --- Loading加载动画控件

    本系列主要使用Qt painter来实现一些基础控件.主要是对平时自行编写的一些自定义控件的总结. 为了简洁.低耦合,我们尽量不使用图片,qrc,ui等文件,而只使用c++的.h和.cpp文件. 由于 ...

  8. Qt 使用QLabel、QMovie加载gif图片实现动态等待窗口

    新建基于Widget的应用程序,在ui的窗口中添加QLabel,对象名label,调整整个窗口大小. 准备loading.gif图片  Widget.cpp  12345678910111213141 ...

  9. VS2015中使用qt开发客户端,QPluginLoader加载dll为null的解决办法

    1,问题重现: 使用vs2015开发一款qt软件,使用了QPluginLoader动态加载插件的方式,调试的时候,发现dll模块没有加载进来,debug发现QPluginLoader的instance ...

  10. 解决QT出现XXXX.dll不能加载问题

    第一步:下载相关动态链接文件(这里以ig4icd32.dll为例子) 下载地址:ig4icd32.dll文件 第二步:把下载的文件放在两个地方,记住!一定得放在两个地方,我试了少一个都不行! C:\W ...

随机推荐

  1. 简单 webapi 登录成功就返回 电脑的进程信息·

    /// <summary> /// 如果登录成功就返回电脑的进程信息 /// </summary> /// <returns></returns> [H ...

  2. 复用对评论和对文章回复的弹层 popup- vant2

    基本样式: ps:当message 即输入的内容的长度为 0 的时候,按钮禁止使用 : <template> <div class="comment-post"& ...

  3. 洛谷P1596水坑计数

    [USACO10OCT] Lake Counting S 题目链接 题面翻译 由于近期的降雨,雨水汇集在农民约翰的田地不同的地方.我们用一个 \(N\times M(1\leq N\leq 100, ...

  4. 对于 Serverless, DevOps, 多云及边缘可观察性的思考与实践

    从单体到微服务,再到 Serverless,应用在逐渐地轻量化.有人可能会有疑问,微服务都还没有顺畅的搭建起来,现在又出了 Serverless,应该怎么办? 其实两者之间并不是一个相互替代的关系.我 ...

  5. 运营商业务系统基于 KubeSphere 的容器化实践

    本篇文章是 KubeSphere 2020 年度 Meetup 上讲师宋磊分享内容整理而成. 大家好,我是宋磊,在运营商的一个科技子公司任职,主要做大数据业务.我主要负责公司的 IaaS 层和 Paa ...

  6. Kubernetes 跨 StorageClass 迁移 Persistent Volumes 完全指南

    大家好,我是米开朗基杨. KubeSphere 3.3.0 (不出意外的话~)本周就要 GA 了,作为一名 KubeSphere 脑残粉,我迫不及待地先安装 RC 版尝尝鲜,一顿操作猛如虎开启所有组件 ...

  7. Python-提高-2

    阅读目录 1.多继承以及MRO顺序 2.再论静态方法和类方法 3.property属性-讲解 4.property属性-应用 5.魔法属性 6.面向对象设计 7.with与"上下文管理器&q ...

  8. 一份阅读量30万+免费且全面的C#/.NET面试宝典

    前言 C#/.NET/.NET Core相关技术常见面试题汇总,不仅仅为了面试而学习,更多的是查漏补缺.扩充知识面和大家共同学习进步.该知识库主要由自己平时学习实践总结.网上优秀文章资料收集(这一部分 ...

  9. vue3 js 学习笔记

    Vue3-js 学习笔记 目录 Vue3-js 学习笔记 目录 前言 reactive 数据绑定 事件绑定 生命函数周期 计算属性-computed props emit-自定义事件 ref-获取元素 ...

  10. Auto-Encoding Variational Bayes (VAE原文)、变分推理

    变分自动编码器的大致概念已经理解了快一年多了,但是其中的数学原理还是没有搞懂,在看到相关的变体时,总会被数学公式卡住.下决心搞懂后,在此记录下我的理解. 公式推导--变分下界 这篇文章提出一种拟合数据 ...