C++(Qt)-GIS開發-簡易瓦片地圖下載器

mahuifa發表於2024-07-06

Qt-GIS開發-簡易瓦片地圖下載器

目錄
  • Qt-GIS開發-簡易瓦片地圖下載器
    • 1、概述
    • 2、安裝openssl
    • 3、實現效果
    • 4、主要程式碼
      • 4.1 演算法函式
      • 4.2 瓦片地圖下載url拼接
      • 4.3 多執行緒下載
    • 5、原始碼地址
    • 6、參考

更多精彩內容
👉個人內容分類彙總 👈
👉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、原始碼地址

  • github
  • gitee

6、參考

  • GIS開發一:OpenLayers線上瓦片資料來源彙總_線上瓦片圖資料-CSDN部落格
  • Bing Maps Tile System - Bing Maps | Microsoft Learn

相關文章