Qt-GIS開發-簡易瓦片地圖下載器
目錄
- Qt-GIS開發-簡易瓦片地圖下載器
- 1、概述
- 2、安裝openssl
- 3、實現效果
- 4、主要程式碼
- 4.1 演算法函式
- 4.2 瓦片地圖下載url拼接
- 4.3 多執行緒下載
- 5、原始碼地址
- 6、參考
更多精彩內容 |
---|
👉個人內容分類彙總 👈 |
👉GIS開發 👈 |
1、概述
- 支援單執行緒、多執行緒下載瓦片地圖。
- 使用QNetworkAccessManager、QNetworkReply實現http、https下載功能;
- 支援下載多樣式arcGis瓦片地圖;
- 支援下載多樣式高德瓦片地圖;
- 支援多樣式Bing地圖下載;
- Qt中https下載功能需要安裝openssl庫。
- 本文中不會詳細說瓦片地圖的原理,寫得好的文章太多了。
開發環境說明
- 系統: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、實現效果
- 無需註冊、無需key進行瓦片地圖下載;
- 地址可能會失效;
- 大量下載可能會限速;
- 僅作為學習使用。
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("<ype=%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