C++(Qt)-GIS开发-简易瓦片地图下载器

Qt-GIS开发-简易瓦片地图下载器

更多精彩内容
👉个人内容分类汇总 👈
👉GIS开发 👈

1、概述

  1. 支持单线程、多线程下载瓦片地图。
  2. 使用QNetworkAccessManager、QNetworkReply实现http、https下载功能;
  3. 支持下载多样式arcGis瓦片地图;
  4. 支持下载多样式高德瓦片地图;
  5. 支持多样式Bing地图下载;
  6. Qt中https下载功能需要安装openssl库。
  7. 本文中不会详细说瓦片地图的原理,写得好的文章太多了。

开发环境说明

  • 系统:Windows11、Ubuntu20.04
  • Qt版本:Qt 5.14.2
  • 编译器:MSVC2017-64、GCC/G++64

2、安装openssl

  • qt使用QNetworkReply/https下载瓦片地图需要ssl支持,qt默认是没有ssl库的;

  • 使用下列代码打印qt版本支持的ssl版本;

    qDebug() << "输出当前QT支持的openSSL版本: " << QSslSocket::sslLibraryBuildVersionString();
    qDebug() << "OpenSSL支持情况: " <<QSslSocket::supportsSsl();
    qDebug() << "OpenSSL运行时SSL库版本: " << QSslSocket::sslLibraryBuildVersionString();
    
  • windows可以下载对应版本的openssl,然后进行安装(轻量级就可以);

  • linux可以通过命令行安装,也可以下载源码自己编译。

  • openssl的github仓库

  • openssl官网

  • 安装后将openssl/bin文件夹下的libcrypto-1_1-x64.dll、libssl-1_1-x64.dll两个动态库拷贝到qt的编译器路径下,注意区分32和64位

    • D:\Qt\Qt5.14.2\5.14.2\msvc2017_64\bin
    • D:\Qt\Qt5.14.2\5.14.2\mingw73_64\bin

3、实现效果

  1. 无需注册、无需key进行瓦片地图下载;
  2. 地址可能会失效;
  3. 大量下载可能会限速;
  4. 仅作为学习使用。

4、主要代码

  • 项目文件结构

4.1 算法函数

  • bingformula.h文件

    #ifndef BINGFORMULA_H
    #define BINGFORMULA_H
    #include <QPoint>
    #include <QtGlobal>
    
    namespace Bing {
    qreal clip(qreal n, qreal min, qreal max);
    qreal clipLon(qreal lon);   // 裁剪经度范围
    qreal clipLat(qreal lat);   // 裁剪纬度范围
    
    uint mapSize(int level);                        // 根据地图级别计算世界地图总宽高(以像素为单位)
    qreal groundResolution(qreal lat, int level);   // 计算地面分辨率
    qreal mapScale(qreal lat, int level, int screenDpi);   // 计算比例尺
    
    QPoint latLongToPixelXY(qreal lon, qreal lat, int level);               // 经纬度转像素 XY坐标
    void pixelXYToLatLong(QPoint pos, int level, qreal& lon, qreal& lat);   // 像素坐标转WGS-84墨卡托坐标
    
    QPoint pixelXYToTileXY(QPoint pos);    // 像素坐标转瓦片编号
    QPoint tileXYToPixelXY(QPoint tile);   // 瓦片编号转像素坐标
    
    QPoint latLongToTileXY(qreal lon, qreal lat, int level);   // 经纬度转瓦片编号
    QPointF tileXYToLatLong(QPoint tile, int level);           // 瓦片编号转经纬度
    
    QString tileXYToQuadKey(QPoint tile, int level);                             // 瓦片编号转QuadKey
    void quadKeyToTileXY(QString quadKey, int& tileX, int& tileY, int& level);   // QuadKey转瓦片编号、级别
    }   // namespace Bing
    #endif   // BINGFORMULA_H
    
    
  • bingformula.cpp文件

    /********************************************************************
     * 文件名: bingformula.cpp
     * 时间:   2024-04-05 21:36:16
     * 开发者:  mhf
     * 邮箱:   1603291350@qq.com
     * 说明:   适用于Bing瓦片地图的算法
     * ******************************************************************/
    #include "bingformula.h"
    #include <qstring.h>
    #include <QtMath>
    
    static const qreal g_EarthRadius = 6'378'137;   // 赤道半径
    
    /**
     * @brief      限定最小值,最大值范围
     * @param n    需要限定的值
     * @param min
     * @param max
     * @return
     */
    qreal Bing::clip(qreal n, qreal min, qreal max)
    {
        n = qMax(n, min);
        n = qMin(n, max);
        return n;
    }
    
    /**
     * @brief      限定经度范围值,防止超限,经度范围[-180, 180]
     * @param lon  输入的经度
     * @return     裁剪后的经度
     */
    qreal Bing::clipLon(qreal lon)
    {
        return clip(lon, -180.0, 180);
    }
    
    /**
     * @brief      限定纬度范围值,防止超限,经度范围[-85.05112878, 85.05112878]
     * @param lat  输入的纬度
     * @return     裁剪后的纬度
     */
    qreal Bing::clipLat(qreal lat)
    {
        return clip(lat, -85.05112878, 85.05112878);
    }
    
    /**
     * @brief       根据输入的瓦片级别计算全地图总宽高,适用于墨卡托投影
     * @param level 1-23(bing地图没有0级别,最低级别为1,由4块瓦片组成)
     * @return      以像素为单位的地图宽度和高度。
     */
    uint Bing::mapSize(int level)
    {
        uint w = 256;   // 第0级别为256*256
        return (w << level);
    }
    
    /**
     * @brief        计算指定纬度、级别的地面分辨率(不同纬度分辨率不同)
     * @param lat    纬度
     * @param level  地图级别 1-23(bing地图没有0级别,最低级别为1,由4块瓦片组成)
     * @return       地面分辨率 单位(米/像素)
     */
    qreal Bing::groundResolution(qreal lat, int level)
    {
        lat = clipLat(lat);
        return qCos(lat * M_PI / 180) * 2 * M_PI * g_EarthRadius / mapSize(level);
    }
    
    /**
     * @brief           计算地图比例尺,地面分辨率和地图比例尺也随纬度而变化
     * @param lat       纬度
     * @param level     地图级别 1-23(bing地图没有0级别,最低级别为1,由4块瓦片组成)
     * @param screenDpi 屏幕分辨率,单位为点/英寸  通常为 96 dpi
     * @return          地图比例尺 1:N(地图上1厘米表示实际N厘米)
     */
    qreal Bing::mapScale(qreal lat, int level, int screenDpi)
    {
        return groundResolution(lat, level) * screenDpi / 0.0254;   // 1英寸等于0.0254米
    }
    
    /**
     * @brief         将一个点从纬度/经度WGS-84墨卡托坐标(以度为单位)转换为指定细节级别的像素XY坐标。
     * @param lon     经度
     * @param lat     纬度
     * @param level   地图级别
     * @return        像素坐标
     */
    QPoint Bing::latLongToPixelXY(qreal lon, qreal lat, int level)
    {
        lon = clipLon(lon);
        lat = clipLat(lat);
    
        qreal x = (lon + 180) / 360;
        qreal sinLat = qSin(lat * M_PI / 180);
        qreal y = 0.5 - qLn((1 + sinLat) / (1 - sinLat)) / (4 * M_PI);
    
        uint size = mapSize(level);
        qreal pixelX = x * size + 0.5;
        pixelX = clip(pixelX, 0, size - 1);
        qreal pixelY = y * size + 0.5;
        pixelY = clip(pixelY, 0, size - 1);
    
        return QPoint(pixelX, pixelY);
    }
    
    /**
     * @brief         将像素从指定细节级别的像素XY坐标转换为经纬度WGS-84坐标(以度为单位)
     * @param pos    像素坐标
     * @param level
     * @param lon
     * @param lat
     */
    void Bing::pixelXYToLatLong(QPoint pos, int level, qreal& lon, qreal& lat)
    {
        uint size = mapSize(level);
        qreal x = (clip(pos.x(), 0, size - 1) / size) - 0.5;
        qreal y = 0.5 - (clip(pos.y(), 0, size - 1) / size);
        lon = x * 360;
        lat = 90 - (360 * qAtan(qExp(-y * 2 * M_PI)) / M_PI);
    }
    
    /**
     * @brief     像素坐标转瓦片编号
     * @param pos  像素坐标
     * @return    瓦片编号
     */
    QPoint Bing::pixelXYToTileXY(QPoint pos)
    {
        int x = pos.x() / 256;
        int y = pos.y() / 256;
        return QPoint(x, y);
    }
    
    /**
     * @brief       瓦片编号转像素坐标
     * @param tile  瓦片编号
     * @return      像素坐标
     */
    QPoint Bing::tileXYToPixelXY(QPoint tile)
    {
        int x = tile.x() * 256;
        int y = tile.y() * 256;
        return QPoint(x, y);
    }
    
    /**
     * @brief       经纬度转瓦片编号
     * @param lon
     * @param lat
     * @param level
     * @return
     */
    QPoint Bing::latLongToTileXY(qreal lon, qreal lat, int level)
    {
        return pixelXYToTileXY(latLongToPixelXY(lon, lat, level));
    }
    
    /**
     * @brief         瓦片编号转经纬度
     * @param tile
     * @param level
     * @return       经纬度 x:经度  y纬度
     */
    QPointF Bing::tileXYToLatLong(QPoint tile, int level)
    {
        qreal lon = 0;
        qreal lat = 0;
        QPoint pos = tileXYToPixelXY(tile);
        pixelXYToLatLong(pos, level, lon, lat);
        return QPointF(lon, lat);
    }
    
    /**
     * @brief         瓦片编号转 bing请求的QuadKey
     * @param tile   瓦片编号
     * @param level  瓦片级别
     * @return
     */
    QString Bing::tileXYToQuadKey(QPoint tile, int level)
    {
        QString key;
        for (int i = level; i > 0; i--)
        {
            char digit = '0';
            int mask = 1 << (i - 1);
            if ((tile.x() & mask) != 0)
            {
                digit++;
            }
            if ((tile.y() & mask) != 0)
            {
                digit += 2;
            }
            key.append(digit);
        }
        return key;
    }
    
    /**
     * @brief            将一个QuadKey转换为瓦片XY坐标。
     * @param quadKey
     * @param tileX      返回瓦片X编号
     * @param tileY      返回瓦片Y编号
     * @param level      返回瓦片等级
     */
    void Bing::quadKeyToTileXY(QString quadKey, int& tileX, int& tileY, int& level)
    {
        tileX = 0;
        tileY = 0;
        level = quadKey.count();
        QByteArray buf = quadKey.toUtf8();
        for (int i = level; i > 0; i--)
        {
            int mask = 1 << (i - 1);
            switch (buf.at(i - 1))
            {
            case '0':
                break;
            case '1':
                tileX |= mask;
                break;
            case '2':
                tileY |= mask;
                break;
            case '3':
                tileX |= mask;
                tileY |= mask;
                break;
            default:
                break;
            }
        }
    }
    
    

4.2 瓦片地图下载url拼接

  • mapinput.h

    #ifndef MAPINPUT_H
    #define MAPINPUT_H
    
    #include <QWidget>
    #include "mapStruct.h"
    
    namespace Ui {
    class MapInput;
    }
    
    class MapInput : public QWidget
    {
        Q_OBJECT
    
    public:
        explicit MapInput(QWidget *parent = nullptr);
        ~MapInput();
    
        const QList<ImageInfo> &getInputInfo();       // 获取下载地图所需的输入信息
    
    private:
        // ArcGis
        void initArcGis();
        void getArcGisMapInfo();
        // 高德
        void initAMap();
        void getAMapInfo();
        // Bing地图
        void initBing();
        void getBingMapInfo();
    
    private:
        Ui::MapInput *ui;
        QList<ImageInfo> m_infos;                // 保存下载瓦片图片的信息
    };
    
    #endif // MAPINPUT_H
    
    
  • mapinput.cpp

    /********************************************************************
     * 文件名: mapinput.cpp
     * 时间:   2024-01-19 22:22:37
     * 开发者:  mhf
     * 邮箱:   1603291350@qq.com
     * 说明:   生成地图下载的输入信息
     * ******************************************************************/
    #include "mapinput.h"
    #include "bingformula.h"
    #include "formula.h"
    #include "ui_mapinput.h"
    #include <QDebug>
    
    MapInput::MapInput(QWidget* parent)
        : QWidget(parent)
        , ui(new Ui::MapInput)
    {
        ui->setupUi(this);
    
        initArcGis();
        initAMap();
        initBing();
    }
    
    MapInput::~MapInput()
    {
        delete ui;
    }
    
    /**
     * @brief 填入ArcGis下载地图类型
     */
    void MapInput::initArcGis()
    {
        for (int i = 0; i < 23; i++)
        {
            ui->com_z->addItem(QString("%1").arg(i), i);
        }
        ui->com_type->addItem("NatGeo_World_Map");
        ui->com_type->addItem("USA_Topo_Maps ");
        ui->com_type->addItem("World_Imagery");
        ui->com_type->addItem("World_Physical_Map");
        ui->com_type->addItem("World_Shaded_Relief");
        ui->com_type->addItem("World_Street_Map");
        ui->com_type->addItem("World_Terrain_Base");
        ui->com_type->addItem("World_Topo_Map");
        ui->com_type->addItem("Canvas/World_Dark_Gray_Base");
        ui->com_type->addItem("Canvas/World_Dark_Gray_Reference");
        ui->com_type->addItem("Canvas/World_Light_Gray_Base");
        ui->com_type->addItem("Canvas/World_Light_Gray_Reference");
        ui->com_type->addItem("Elevation/World_Hillshade_Dark");
        ui->com_type->addItem("Elevation/World_Hillshade");
        ui->com_type->addItem("Ocean/World_Ocean_Base");
        ui->com_type->addItem("Ocean/World_Ocean_Reference");
        ui->com_type->addItem("Polar/Antarctic_Imagery");
        ui->com_type->addItem("Polar/Arctic_Imagery");
        ui->com_type->addItem("Polar/Arctic_Ocean_Base");
        ui->com_type->addItem("Polar/Arctic_Ocean_Reference");
        ui->com_type->addItem("Reference/World_Boundaries_and_Places_Alternate ");
        ui->com_type->addItem("Reference/World_Boundaries_and_Places");
        ui->com_type->addItem("Reference/World_Reference_Overlay");
        ui->com_type->addItem("Reference/World_Transportation");
        ui->com_type->addItem("Specialty/World_Navigation_Charts");
    
        // 填入下载格式
        ui->com_format->addItem("jpg");
        ui->com_format->addItem("png");
        ui->com_format->addItem("bmp");
    }
    
    /**
     * @brief   计算并返回需要下载的瓦片地图信息
     * @return
     */
    const QList<ImageInfo>& MapInput::getInputInfo()
    {
        m_infos.clear();   // 清除之前的内容
    
        switch (ui->tabWidget->currentIndex())   // 判断是什么类型的地图源
        {
        case 0:   // ArcGis
            {
                getArcGisMapInfo();   // 计算ArcGis下载信息
                break;
            }
        case 1:
            {
                getAMapInfo();   // 计算高德地图下载信息
                break;
            }
        case 2:
            {
                getBingMapInfo();   // 计算bing地图下载信息
                break;
            }
        default:
            break;
        }
    
        qDebug() << "瓦片数:" << m_infos.count();
    
        return m_infos;
    }
    
    /**
     * @brief   通过输入地图信息计算需要下载的瓦片图信息,下载ArcGIS地图,WGS84坐标系,Web墨卡托投影,z y x输入
     */
    void MapInput::getArcGisMapInfo()
    {
        static QString url = "https://server.arcgisonline.com/arcgis/rest/services/%1/MapServer/tile/%2/%3/%4.%5";
    
        int z = ui->com_z->currentData().toInt();
        QString type = ui->com_type->currentText();
        QString format = ui->com_format->currentText();
        QStringList lt = ui->line_LTGps->text().trimmed().split(',');   // 左上角经纬度
        QStringList rd = ui->line_RDGps->text().trimmed().split(',');   // 右下角经纬度
        if (lt.count() != 2 || rd.count() != 2)
            return;                                    // 判断输入是否正确
        int ltX = lonTotile(lt.at(0).toDouble(), z);   // 计算左上角瓦片X
        int ltY = latTotile(lt.at(1).toDouble(), z);   // 计算左上角瓦片Y
        int rdX = lonTotile(rd.at(0).toDouble(), z);   // 计算右下角瓦片X
        int rdY = latTotile(rd.at(1).toDouble(), z);   // 计算右下角瓦片Y
    
        ImageInfo info;
        info.z = z;
        info.format = format;
        for (int x = ltX; x <= rdX; x++)
        {
            info.x = x;
            for (int y = ltY; y <= rdY; y++)
            {
                info.y = y;
                info.url = url.arg(type).arg(z).arg(y).arg(x).arg(format);
                m_infos.append(info);
            }
        }
    }
    
    /**
     * @brief 初始化高德地图下载选项信息
     */
    void MapInput::initAMap()
    {
        for (int i = 1; i < 5; i++)
        {
            ui->com_amapPrefix->addItem(QString("wprd0%1").arg(i));
        }
        for (int i = 1; i < 5; i++)
        {
            ui->com_amapPrefix->addItem(QString("webst0%1").arg(i));
        }
        for (int i = 0; i < 19; i++)
        {
            ui->com_amapZ->addItem(QString("%1").arg(i), i);
        }
        // 语言设置
        ui->com_amapLang->addItem("中文", "zh_cn");
        ui->com_amapLang->addItem("英文", "en");
        // 地图类型
        ui->com_amapStyle->addItem("卫星影像图", 6);
        ui->com_amapStyle->addItem("矢量路网", 7);
        ui->com_amapStyle->addItem("影像路网", 8);        // 支持png透明背景
        ui->com_amapStyle->addItem("卫星+影像路网", 9);   // 支持png透明背景
        // 图片尺寸,只在7 8生效
        ui->com_amapScl->addItem("256x256", 1);
        ui->com_amapScl->addItem("512x512", 2);
    
        // 填入下载格式
        ui->com_amapFormat->addItem("jpg");
        ui->com_amapFormat->addItem("png");
        ui->com_amapFormat->addItem("bmp");
    }
    
    /**
     * @brief 计算高德地图瓦片下载信息
     */
    void MapInput::getAMapInfo()
    {
        static QString url = "https://%1.is.autonavi.com/appmaptile?";
    
        int z = ui->com_amapZ->currentData().toInt();
        QString format = ui->com_amapFormat->currentText();
        QStringList lt = ui->line_LTGps->text().trimmed().split(',');   // 左上角经纬度
        QStringList rd = ui->line_RDGps->text().trimmed().split(',');   // 右下角经纬度
        if (lt.count() != 2 || rd.count() != 2)
            return;                                    // 判断输入是否正确
        int ltX = lonTotile(lt.at(0).toDouble(), z);   // 计算左上角瓦片X
        int ltY = latTotile(lt.at(1).toDouble(), z);   // 计算左上角瓦片Y
        int rdX = lonTotile(rd.at(0).toDouble(), z);   // 计算右下角瓦片X
        int rdY = latTotile(rd.at(1).toDouble(), z);   // 计算右下角瓦片Y
    
        ImageInfo info;
        info.z = z;
        info.format = format;
        int style = ui->com_amapStyle->currentData().toInt();
        int count = 1;
        if (style == 9)
        {
            count = 2;   // 如果是下载卫星图 + 路网图则循环两次
        }
    
        for (int i = 0; i < count; i++)
        {
            if (count == 2)
            {
                if (i == 0)
                {
                    style = 6;   // 第一次下载卫星图
                    info.format = "jpg";
                }
                else
                {
                    style = 8;             // 第二次下载路网图
                    info.format = "png";   // 如果同时下载卫星图和路网图则路网图为透明png格式
                }
            }
            QString tempUrl = url.arg(ui->com_amapPrefix->currentText());                     // 设置域名
            tempUrl += QString("&style=%1").arg(style);                                       // 设置地图类型
            tempUrl += QString("&lang=%1").arg(ui->com_amapLang->currentData().toString());   // 设置语言
            tempUrl += QString("&scl=%1").arg(ui->com_amapScl->currentData().toInt());        // 设置图片尺寸,只在7 8生效
            tempUrl += QString("&ltype=%1").arg(ui->spin_amapLtype->value());                 // 设置图片中的信息,只有 7 8有效
    
            for (int x = ltX; x <= rdX; x++)
            {
                info.x = x;
                for (int y = ltY; y <= rdY; y++)
                {
                    info.url = tempUrl + QString("&x=%1&y=%2&z=%3").arg(x).arg(y).arg(z);
                    info.y = y;
                    m_infos.append(info);
                }
            }
        }
    }
    
    /**
     * @brief 初始化Bing地图配置
     */
    void MapInput::initBing()
    {
        // 服务器
        for (int i = 0; i < 8; i++)
        {
            ui->com_bingPrefix->addItem(QString("%1").arg(i));
        }
        // 地图语言
        ui->com_bingLang->addItem("中文", "zh-cn");
        ui->com_bingLang->addItem("英语", "en-US");
        // 地图类型
        ui->com_bingType->addItem("卫星地图", "a");
        ui->com_bingType->addItem("普通地图", "r");
        ui->com_bingType->addItem("混合地图", "h");
    
        ui->com_bingCstl->addItem("默认", "w4c");
        ui->com_bingCstl->addItem("白天", "vb");    // 白天道路地图
        ui->com_bingCstl->addItem("夜晚", "vbd");   // 夜晚道路图
        // 瓦片等级
        for (int i = 1; i < 21; i++)
        {
            ui->com_bingZ->addItem(QString("%1").arg(i));
        }
        // 填入下载格式
        ui->com_bingFormat->addItem("jpg");
        ui->com_bingFormat->addItem("png");
        ui->com_bingFormat->addItem("bmp");
    }
    
    /**
     * @brief 计算Bing地图的下载信息(这些url可能会失效,后续会使用其他方式下载)
     *  https://learn.microsoft.com/en-us/bingmaps/rest-services/directly-accessing-the-bing-maps-tiles
     */
    void MapInput::getBingMapInfo()
    {
        //https://r1.tiles.ditu.live.com/tiles/r1321001.png?g=1001&mkt=zh-cn
        //http://dynamic.t2.tiles.ditu.live.com/comp/ch/r1321001.png?it=G,OS,L&mkt=en-us&cstl=w4c&ur=cn
        //http://ecn.t{0}.tiles.virtualearth.net/tiles/{1}{2}.png? g={4}
        //https://t0.dynamic.tiles.ditu.live.com/comp/ch/1320300313132?mkt=zh-CN&ur=CN&it=G,RL&n=z&og=894&cstl=vb
        //https://t1.dynamic.tiles.ditu.live.com/comp/ch/13203012200201?mkt=zh-CN&ur=cn&it=G,RL&n=z&og=894&cstl=vbd
        //https://dynamic.t1.tiles.ditu.live.com/comp/ch/1320300313313?it=Z,TF&L&n=z&key=AvquUWQgfy7VPqHn9ergJsp3Q_EiUft0ed70vZsX0_aqPABBdK07OkwrXWoGXsTG&ur=cn&cstl=vbd
    
    #define USE_URL 1
    #if (USE_URL == 0)
        // https://r1.tiles.ditu.live.com/tiles/r1321001.png?g=1001&mkt=zh-cn
        static QString url = "https://r%1.tiles.ditu.live.com/tiles/%2%3.%4?g=1001&mkt=%5";   // 街道图r支持中文
    #elif (USE_URL == 1)
        // http://dynamic.t2.tiles.ditu.live.com/comp/ch/r1321001.png?it=G,OS,L&mkt=en-us&cstl=w4c&ur=cn
        static QString url = "http://dynamic.t%1.tiles.ditu.live.com/comp/ch/%2%3.%4?it=G,OS,L&mkt=%5&cstl=%6&ur=cn";
    #endif
        int z = ui->com_bingZ->currentText().toInt();
        QStringList lt = ui->line_LTGps->text().trimmed().split(',');   // 左上角经纬度
        QStringList rd = ui->line_RDGps->text().trimmed().split(',');   // 右下角经纬度
        if (lt.count() != 2 || rd.count() != 2)
            return;                                    // 判断输入是否正确
        int ltX = lonTotile(lt.at(0).toDouble(), z);   // 计算左上角瓦片X
        int ltY = latTotile(lt.at(1).toDouble(), z);   // 计算左上角瓦片Y
        int rdX = lonTotile(rd.at(0).toDouble(), z);   // 计算右下角瓦片X
        int rdY = latTotile(rd.at(1).toDouble(), z);   // 计算右下角瓦片Y
    
        QString format = ui->com_bingFormat->currentText();
        ImageInfo info;
        info.z = z;
        info.format = format;
        int prefix = ui->com_bingPrefix->currentIndex();
        QString lang = ui->com_bingLang->currentData().toString();   // 语言
        QString type = ui->com_bingType->currentData().toString();   // 类型
        QString cstl = ui->com_bingCstl->currentData().toString();   // 样式
    
        QPoint point;
        for (int x = ltX; x <= rdX; x++)
        {
            info.x = x;
            point.setX(x);
            for (int y = ltY; y <= rdY; y++)
            {
                info.y = y;
                point.setY(y);
                QString quadKey = Bing::tileXYToQuadKey(point, z);   // 将xy转为quadkey
    #if (USE_URL == 0)
                info.url = url.arg(prefix).arg(type).arg(quadKey).arg(format).arg(lang);
    #elif (USE_URL == 1)
                info.url = url.arg(prefix).arg(type).arg(quadKey).arg(format).arg(lang).arg(cstl);
    #endif
                m_infos.append(info);
            }
        }
    }
    
    

4.3 多线程下载

  • downloadthreads.h

    #ifndef DOWNLOADTHREADS_H
    #define DOWNLOADTHREADS_H
    
    #include "mapStruct.h"
    #include <QFutureWatcher>
    #include <QObject>
    
    class DownloadThreads : public QObject
    {
        Q_OBJECT
    public:
        explicit DownloadThreads(QObject* parent = nullptr);
        ~DownloadThreads();
    
        // 传入需要下载的瓦片信息
        void getImage(QList<ImageInfo> infos);
        void quit();   // 退出下载
    
    signals:
        void finished(ImageInfo info);   // 返回下载后的瓦片,由于QImage为共享内存,所以传递不需要考虑太多性能
    
    private:
        QFuture<void> m_future;
        QList<ImageInfo> m_infos;
    };
    
    #endif   // DOWNLOADTHREADS_H
    
    
  • downloadthreads.cpp

    /********************************************************************
     * 文件名: downloadthreads.cpp
     * 时间:   2024-03-31 20:32:58
     * 开发者:  mhf
     * 邮箱:   1603291350@qq.com
     * 说明:   多线程下载瓦片地图
     * ******************************************************************/
    #include "downloadthreads.h"
    #include <QtConcurrent>
    #include <qnetworkaccessmanager.h>
    #include <qnetworkreply.h>
    
    static DownloadThreads* g_this = nullptr;
    DownloadThreads::DownloadThreads(QObject *parent) : QObject(parent)
    {
        g_this = this;  // 记录当前 this指针,用于传递信号
    }
    
    DownloadThreads::~DownloadThreads()
    {
        g_this = nullptr;
        quit();
    }
    
    /**
     * @brief       下载瓦片
     * @param info
     * @return
     */
    void getUrl(ImageInfo info)
    {
        QNetworkAccessManager manager;
        QNetworkReply* reply = manager.get(QNetworkRequest(QUrl(info.url)));
        // 等待返回
        QEventLoop loop;
        QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
        loop.exec();
    
        if(reply->error() == QNetworkReply::NoError)
        {
            QByteArray buf = reply->readAll();
            info.img.loadFromData(buf);
        }
        else
        {
            info.count++;
            if(info.count < 3)
            {
                getUrl(info);   // 下载失败重新下载
                return;
            }
            else
            {
                qWarning() << "下载失败:" << reply->errorString();
            }
        }
        if(g_this)
        {
            emit g_this->finished(info);  // 通过信号将下载后的瓦片传出去
        }
    }
    
    /**
     * @brief         调用线程池下载瓦片
     * @param infos
     */
    void DownloadThreads::getImage(QList<ImageInfo> infos)
    {
        m_infos = infos;    // 这里不能使用infos,因为会在函数退出释放
    #if 0   // 由于map使用的是全局线程池,所以可以查看、设置线程数
        qDebug() <<QThreadPool::globalInstance()->maxThreadCount();   // 查看最大线程数
        QThreadPool::globalInstance()->setMaxThreadCount(1);          // 设置最大线程数
    #endif
        m_future = QtConcurrent::map(m_infos, getUrl);
    }
    
    /**
     * @brief 退出下载
     */
    void DownloadThreads::quit()
    {
        if(m_future.isRunning())   // 判断是否在运行
        {
            m_future.cancel();               // 取消下载
            m_future.waitForFinished();      // 等待退出
        }
    }
    
    

5、源码地址

6、参考

posted @ 2024-07-06 15:22  mahuifa  阅读(345)  评论(0编辑  收藏  举报