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:QtQt5.14.25.14.2msvc2017_64bin
    • D:QtQt5.14.25.14.2mingw73_64bin

3、实现效果

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

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

4、主要代码

  • 项目文件结构

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

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、参考

发表评论

评论已关闭。

相关文章