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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/777606.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

连锁门店如何快速联网

随着新零售业态的发展&#xff0c;连锁门店的运营模式逐渐转为数字化运营&#xff0c;新增了诸如收银PoS、扫码枪、摄像头等数字化终端。这些数字化的业务应用都需要依托稳定可靠的网络才能正常运转&#xff0c;在这样的背景下&#xff0c;连锁门店对网络连接的需求显得尤为关键…

C++下Protobuf学习

C下Protobuf简单学习 Protobuf&#xff08;Protocol Buffers&#xff09;协议是一种由 Google 开发的高效的、跨语言的、平台无关的数据序列化协议&#xff0c;提供二进制序列化格式和相关的技术&#xff0c;它用于高效地序列化和反序列化结构化数据&#xff0c;通常用于网络通…

WordPress网站违法关键词字过滤插件下载text-filter

插件下载地址&#xff1a;https://www.wpadmin.cn/2025.html 插件介绍 WordPress网站违法关键词字过滤插件text-filter由本站原创开发,支持中英文关键字自动替换成**号&#xff0c;可以通过自定义保存修改按钮增加“预设关键字”&#xff0c;也可以导入定义好的txt文本形式的关…

single_test_funi.py: error: the following arguments are required: img

parser.add_argument(img, defaultS/1.jpg, helpImage file) 当你已经指定了文件路径&#xff0c;还是报错怎么办&#xff1f; parser.add_argument(img, nargs?, defaultS/1.jpg, helpImage file) nargs? 表示 config 参数是可选的。如果用户没有提供这个参数&#xff0c…

【ARMv8/v9 GIC 系列 5.6 -- GIC 超优先级中断详细介绍】

请阅读【ARM GICv3/v4 实战学习 】 文章目录 Interrupt superpriority超优先级中断的特性和应用Physical interface interrupt signalsPhysical Group 1 Non-NMI for Current Security StatePhysical Group 1 for Other Security State, or a Group 0 Non-NMIPhysical Group 1 …

JVM原理(十八):JVM虚拟机的编译器优化技术

1. 编译器优化技术 编译器的目标虽然是做程序代码翻译为本地机器 码的工作&#xff0c;但其实难点并不在于能不能成功翻译出机器码&#xff0c;输出代码优化质量的高低才是决定编译器优秀与否的关键。 1.1. 优化技术概览 即时编译器对这些代码优化变换是建立在代码的中间表示…

基于Android Studio点餐项目,点餐app

目录 项目介绍 图片展示 运行环境 获取方式 项目介绍 实现登录、注册、注销功能&#xff0c;退出登录等功能&#xff0c; 以及基本的选择店铺点餐&#xff0c;加入购物车和结算等功能&#xff0c;以及可以增加或者减少商品的个数&#xff0c; 同时可以同步价格的总量。以…

两年经验前端带你重学前端框架必会的ajax+node.js+webpack+git等技术的个人学习心得、作业及bug记录 Day1

黑马程序员前端AJAX入门到实战全套教程&#xff0c;包含学前端框架必会的&#xff08;ajaxnode.jswebpackgit&#xff09;&#xff0c;一套全覆盖 Day1 你好,我是Qiuner. 为帮助别人少走弯路和记录自己编程学习过程而写博客 这是我的 github https://github.com/Qiuner ⭐️ ​…

沙龙回顾|MongoDB如何充当企业开发加速器?

数据不仅是企业发展转型的驱动力&#xff0c;也是开发者最棘手的问题。前日&#xff0c;MongoDB携手阿里云、NineData在杭州成功举办了“数据驱动&#xff0c;敏捷前行——MongoDB企业开发加速器”技术沙龙。此次活动吸引了来自各行各业的专业人员&#xff0c;共同探讨MongoDB的…

大话C语言:第27篇 内存模型

1 存储硬件概述 现代计算机遵循冯诺依曼体系结果&#xff0c;存储分为&#xff1a; 外部存储器&#xff1a;长期存放数据&#xff0c;掉电不丢失数据。例如&#xff0c;硬盘、flash、rom、u 盘、光盘、磁带。 内部存储器&#xff1a;暂时存放数据&#xff0c;掉电数据丢失。例…

小白学python(第六天)循环之异变

本篇文章给大家讲解的是循环语句&#xff0c;那么闲话少叙&#xff0c;我们进入正题 在c、Java中循环都是三剑客&#xff0c;那么大家可还记得是哪三位剑客吗 剑客一&#xff1a;while循环 剑客二&#xff1a;for循环 剑客三&#xff1a;do{……}while&#xff08;&…

springcloud-alibba之FeignClient

代码地址&#xff1a;springcloud系列: springcloud 组件分析拆解 1.FeignClient的集成 springboot版本&#xff1a;3.1.5 springcloud组件版本&#xff1a;2022.0.4 nacos客户端的版本&#xff1a;2.3.2 1.引pom 这里引入了nacos和feginclient的版本 <dependency>…

MongoDB 单节点升级为副本集高可用集群(1主1从1仲裁)

作者介绍&#xff1a;老苏&#xff0c;10余年DBA工作运维经验&#xff0c;擅长Oracle、MySQL、PG、Mongodb数据库运维&#xff08;如安装迁移&#xff0c;性能优化、故障应急处理等&#xff09; 公众号&#xff1a;老苏畅谈运维 欢迎关注本人公众号&#xff0c;更多精彩与您分享…

SpringBoot 实现视频分段播放(通过进度条来加载视频)

需求&#xff1a;现在我本地电脑中有一个文件夹&#xff0c;文件夹中都是视频&#xff0c;需要实现视频播放的功能。 问题&#xff1a;如果通过类似 SpringBoot static 文件夹的方式来实现&#xff0c;客户端要下载好完整的视频之后才可以播放&#xff0c;并且服务端也会占用大…

计算机网络之以太网

上文内容&#xff1a;总线局域网以及冲突的解决方法 1.以太网的起源 1.1起源 60年代末期&#xff0c;夏威夷大学Norman Abramson等研制ALOHA无线网络系统,实现Oahu岛上的主机和其它岛及船上的读卡机和终端通信&#xff1b; 出境信道地址&#xff1a;主机到终端&#xff1…

vue3 + 百度地图 实现多坐标生成轨迹的两种种方式

本次依然是关于百度地图中常见的一个问题&#xff0c;此次共使用了两种方式并做了一些分析及处理&#xff0c;希望有所帮助。如有问题可以评论或私信。 一、便捷方式 优点&#xff1a;便捷&#xff0c;所用的api方法是根据坐标进行计算后绘制路线&#xff0c;所以路线相对准确…

制定事件响应计划的四个关键步骤,如何做到风险闭环

一个有效的安全事件响应策略的关键组成部分有哪些&#xff1f;一个有效的安全事件响应策略包括四个关键组成部分&#xff0c;它们协同工作以确保对网络安全问题的快速和有效响应。 一个有效的安全事件响应策略的关键组成部分有哪些&#xff1f; 一个有效的安全事件响应策略包括…

Java常用算法集合扩容机制分析

基础篇 基础篇要点&#xff1a;算法、数据结构、基础设计模式 1. 二分查找 要求 能够用自己语言描述二分查找算法能够手写二分查找代码能够解答一些变化后的考法 算法描述 前提&#xff1a;有已排序数组 A&#xff08;假设已经做好&#xff09; 定义左边界 L、右边界 R&…

SQLite 命令行客户端 + Windows 批处理应用

SQLite 命令行客户端 Windows 批处理应用 下载 SQLite 客户端1. Bat 辅助脚本1. 执行SQL.bat执行 2. 导出Excel.bat执行效果 3. 导出HTML.bat执行效果 4. 清空-订单表.bat 2. 测试 SQL1. 创建订单表.sql2. 插入订单表.sql3. 查询订单表.sql4. 清空订单表.sql5. 删除订单表.sql…

linux驱动编程 - kfifo先进先出队列

简介&#xff1a; kfifo是Linux Kernel里面的一个 FIFO&#xff08;先进先出&#xff09;数据结构&#xff0c;它采用环形循环队列的数据结构来实现&#xff0c;提供一个无边界的字节流服务&#xff0c;并且使用并行无锁编程技术&#xff0c;即当它用于只有一个入队线程和一个出…