【无标题】

[QtQuick]实现离线地图

  • Qt版本:5.12.8

1 需求分析

使用Qt实现离线地图,大多数软件是通过GraphicsView框架,结合瓦片地图相关算法来实现的。但这些绝大多数都不太讨喜。从项目时间、成本和质量的角度考虑,我们需要一个开发周期短、人力成本低、软件质量有保障的方案。

自QtLocation 5.0开始,Qt推出了Map QML Type,这意味着我们可以使用Qt自带的地图,而不用自己再去实现非常底层的算法了。

The Map type is used to display a map or image of the Earth, with the capability to also display interactive objects tied to the map’s surface.

2 方案设计

从Qt文档可以看出,Qt通过Map类型作为地图显示介质,通过六种地图插件提供数据驱动。地图插件见下表:

插件名称描述
Qt Location Esri PluginUses Esri for location services.
Qt Location HERE PluginUses the relevant services provided by HERE.
Qt Location Items Overlay PluginProvides an empty map intended to be used as background for an overlay layers for map items.
Qt Location Mapbox GL PluginUses Mapbox GL for location services.
Qt Location Mapbox PluginUses Mapbox for location services.
Qt Location Open Street Map PluginUses Open Street Map and related services.

最后一个Qt Location Open Street Map Plugin,这个插件默认就支持离线地图。先罗列OSM支持的几种地图加载模式:

  1. 网络地图

    通过TMS瓦片服务器获取瓦片地图。

    OSM默认提供TMS服务器配置地址,在本机搭建一个TMS瓦片服务器,就能实现加载本地瓦片地图。这个方案不够高效,放弃该方案

  2. 离线地图

    加载本机存储介质上的瓦片地图。

    按照固定格式命名瓦片地图文件,就可以实现加载本地瓦片地图。这个方案可行,但有优化的地方

  3. 缓存地图

    读取网络地图后,存放在本机的缓存瓦片地图。这属于Qt内部的缓存机制,暂时不考虑入手

从实现离线的地图的目的来说,前两种方式都可行。但为了简单可靠,我们还是从它离线地图的功能开始研究。离线地图需要按照的固定命名格式,但我们下载的资源碰巧不符合;换个方向从源码入手,修改其读取文件名称的规则又有了新的解决方案。方案总结如下:

  • 按照官方的命名规则使用离线地图。
  • 修改官方源码,定义我们自己的瓦片命名规则。
2.1 固定瓦片规则

按照官方的规定,瓦片地图需要按照osm_100-<l|h>-<map_id>-<z>-<x>-<y>.<extension>的命名方式存放在同一个文件夹。

市面上常见地图下载软件是按照z/x/y的形式保存的瓦片地图,所以下载完瓦片地图后,需要批量修改文件名称以适应OSM的要求,命名规则详见QtLocation: using offline map tiles with the OpenStreetMap plugin

通过该方案,每一次下载新地图都需要重新命名。

2.2 自定义瓦片规则

修改OSM的源码,再固定瓦片命名规则的基础上,增加主流的文件命名规则。以Arcgis地图为例,在本地是以z/x/y.png(jpg)的方式存放,其中z代表层级,x代表x轴瓦片编号,y代表瓦片编号。找到源码里面关于读取离线地图的代码,然后修改其加载我们自定义规则的瓦片数据。设计其加载规则首先是官方规则,如果没有找到文件,那么在加载我们的自定义规则。我们定义瓦片文件层次结构如下:

  1. 第一层:瓦片根路径;
  2. 第二层:地图类型(street, satellite, cycle, transit, night-transit, terrain, hiking);
  3. 第三层:地图层级(z);
  4. 第四层:水平瓦片编号(x);
  5. 第五层:竖直瓦片编号(y)。

例如:googlemap/satellite/10/258/346.png

通过该方案,一劳永逸。

3 实现

3.1 批量命名

通过Python、C++等都可以实现批量命名。具体实现略。

3.2 源码定制

我们目的是将官方的osm插件修改为mud(或其他名称)插件。修改源码分三步:1. 安装源码 2. 修改源码 3. 编译运行

3.2.1 源码

安装源码有三种办法:

  1. 安装Qt的时候,勾选上Sources(另外建议勾选MinGW,比MSVC能够调试更多源码);
  2. 下载完整源码包解压,或者单独下载 QtLocation源码包解压 (链接以Qt5.12.8为例);
  3. 从Github克隆 https://github.com/qt/qt5

接下来为修改源码做准备:

  1. QtCreator添加子目录项目命名为MudMap(或其他名称),再添加Qt Quick Application - Empty项目命名为MudViewer(或其他名称,后面用于显示地图);

  2. 打开qtlocation\src\plugins\geoservices文件夹,拷贝osm文件夹到MudMap目录并重命名为MudPlugin(或其他名称),再重命名MudPlugin种的osm.proMudPlugin.pro,再重命名MudPlugin种的osm_plugin.promud_plugin.pro以作为后面修改源码的基础;

  3. 打开qtlocation文件夹,拷贝.qmake.conf文件到MudMap的同目录(可以略过该步以观察报错信息);

  4. 修改MudMap.pro文件内容为

    TEMPLATE = subdirs
    SUBDIRS += \
        MudPlugin \
        MudViewer
    

准备好后的工程目录如下:

MudMap

MudPlugin

MudPlugin.pro

*.* h/cpp/json

MudViewer

MudViewer.pro

*.* cpp/qrc/qml

.qmake.conf

MudMap.pro

最后对MudMap工程执行qmake

3.2.2 编码
  • 工程修改

    1. 修改mud_plugin.json第2-3行内容:

       "Keys": ["mud"],
       "Provider": "mud",
      
           
           
      • 修改MudPlugin.pro第1行内容:

         TARGET = qtgeoservices_mud
        
             
             
      • 修改MudPlugin.pro第41-42行内容

        OTHER_FILES += \
         mud_plugin.json
        
             
             
    2. 源码修改

      1. 修改qgeoserviceproviderpluginosm.h第52-53行(这里修改后,就能够编译过了,如果编译不过,那么前面的步骤有误):

            Q_PLUGIN_METADATA(IID "org.qt-project.qt.geoservice.serviceproviderfactory/5.0"
                              FILE "mud_plugin.json")
        
             
             
      2. 修改qgeotiledmappingmanagerengineosm.cpp第72行:

            const QByteArray pluginName = "mud";
        
             
             
      3. 全局替换代码osm.mappingmud.mapping(共36处)

        值得注意的是,Qt内部实现会对输入给地图插件的参数进行过滤。以osm插件为例,如果传递给mud插件的参数包含一个"osm.mapping.offline.directory"参数,而正好Qt发现有一个叫做osm的插件,"osm."开头的参数就不会传递给mud插件。反之,如果把qtgeoservices_osm.dll文件删掉之后,mud就能收到“osm."开头的参数。

      4. qgeofiletilecacheosm.h/cpp添加函数:

        /*!
         * \return 返回自定义规则的文件绝对路径
         */
        QString QGeoFileTileCacheOsm::tileSpecToAbsFilename(const QGeoTileSpec &spec)
        {
            QString subDir;
            QString absFileName;
            // mapID地图类型,范围1-7 (文件夹)
            switch (spec.mapId()) {
            case 1:
                subDir += "/street"; break;
            case 2:
                subDir += "/satellite"; break;
            case 3:
                subDir += "/cycle"; break;
            case 4:
                subDir += "/transit"; break;
            case 5:
                subDir += "/night-transit"; break;
            case 6:
                subDir += "/terrain"; break;
            case 7:
                subDir += "/hiking"; break;
            default:
                break;
            }
            // 地图层级 (文件夹)
            subDir += "/";
            subDir += QString::number(spec.zoom());
            // 水平编号 (文件夹)
            subDir += "/";
            subDir += QString::number(spec.x());
            // 竖直编号 (文件)
            QString fileNameFilter = QString::number(spec.y()) + ".*";
            // 文件过滤,找到第一个可用的瓦片文件
            QDir fileDir = m_offlineDirectory.path() + subDir;
            QStringList validTiles = fileDir.entryList({fileNameFilter});
            if (validTiles.size()) {
                absFileName = fileDir.absoluteFilePath(validTiles.first());
            }
            return absFileName;
        }
        
             
             
      5. qgeofiletilecacheosm.h/cpp修改函数QGeoFileTileCacheOsm::getFromOfflineStorage的实现:

        QSharedPointer<QGeoTileTexture> QGeoFileTileCacheOsm::getFromOfflineStorage(const QGeoTileSpec &spec)
        {
            if (!m_offlineData)
                return QSharedPointer<QGeoTileTexture>();
            int providerId = spec.mapId() - 1;
            if (providerId < 0 || providerId >= m_providers.size())
                return QSharedPointer<QGeoTileTexture>();
            QString fileName;
            const QString fileNameFilter = tileSpecToFilename(spec, QStringLiteral("*"), providerId);
            QStringList validTiles = m_offlineDirectory.entryList({fileNameFilter});
            // 使用osm默认的命名规则
            if (validTiles.size()) {
                fileName = m_offlineDirectory.absoluteFilePath(validTiles.first());
            }
            // 如果osm的规则没有找到瓦片文件,那么就使用自定义规则
            else {
                fileName = tileSpecToAbsFilename(spec);
            }
            QFile file(fileName);
            if (!file.open(QIODevice::ReadOnly))
                return QSharedPointer<QGeoTileTexture>();
            QByteArray bytes = file.readAll();
            file.close();
            QImage image;
            if (!image.loadFromData(bytes)) {
                handleError(spec, QLatin1String("Problem with tile image"));
                return QSharedPointer<QGeoTileTexture>(0);
            }
            addToMemoryCache(spec, bytes, QString());
            return addToTextureCache(spec, image);
        }
        
             
             
    3.2.3 编译运行
    • 编译

      1. 执行qmake;
      2. 构建;
      3. 在构建路径下将plugins\geoservicesgeoservices文件夹拷贝到构建路径MudViewer\debug(或者MudViewer\release)下。
    • 运行

      1. 拷贝测试代码到main.qml执行qmake,注意修改地图中心和根路径:

        import QtQuick 2.12
        import QtQuick.Window 2.12
        import QtLocation 5.12
        import QtPositioning 5.11
        Window {
            id: win
            objectName: "window"
            visible: true
            width: 512
            height: 512
            Map {
                id: map
                anchors.fill: parent
                activeMapType: map.supportedMapTypes[1]  // 1代表卫星地图
                center: QtPositioning.coordinate(40.39, 99.79)  // 这里写地图显示中心
                opacity: 0.999	// 防止透明度引起的Bug(这是Qt的Bug)
                plugin: Plugin {
                    name: 'mud';
                    PluginParameter {
                        name: "mud.mapping.offline.directory"
                        value: 'D:/googlemaps'  // 这里写地图根路径
                    }
                }
            }
        }
        
             
             
      2. 运行效果

        脑补画面---
        
             
             
      3. 发布

        编译生成的geoservices文件夹就可以作为插件使用了,当其他项目使用时,需要geoservices文件夹和exe执行文件在用一个目录(windows平台)。

    4. 总结

    经过修改源码的osm,不仅保持了原有的功能,还有一点小升级。

    除了离线功能外,osm还提供了20多项的参数设置,"osm.mapping.offline.directory"只是其中一项,更多参数参见:Qt Location Open Street Map Plugin-Parameters。因此,mud插件同样支持这些参数设置,只需要将“osm."开头的参数全部以”mud."开头,就能让mud和osm有一样的功能了。

    源代码:

    https://github.com/Mud-Player/MudMap

    • 0
      点赞
    • 0
      收藏
      觉得还不错? 一键收藏
    • 0
      评论
    【优质项目推荐】 1、项目代码均经过严格本地测试,运行OK,确保功能稳定后才上传平台。可放心下载并立即投入使用,若遇到任何使用问题,随时欢迎私信反馈与沟通,博主会第一时间回复。 2、项目适用于计算机相关专业(如计科、信息安全、数据科学、人工智能、通信、物联网、自动化、电子信息等)的在校学生、专业教师,或企业员工,小白入门等都适用。 3、该项目不仅具有很高的学习借鉴价值,对于初学者来说,也是入门进阶的绝佳选择;当然也可以直接用于 毕设、课设、期末大作业或项目初期立项演示等。 3、开放创新:如果您有一定基础,且热爱探索钻研,可以在此代码基础上二次开发,进行修改、扩展,创造出属于自己的独特应用。 欢迎下载使用优质资源!欢迎借鉴使用,并欢迎学习交流,共同探索编程的无穷魅力! 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip
    提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

    “相关推荐”对你有帮助么?

    • 非常没帮助
    • 没帮助
    • 一般
    • 有帮助
    • 非常有帮助
    提交
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值