C++(Qt)-GIS开发-简易瓦片地图下载器
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*256return (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_OBJECTpublic:explicit MapInput(QWidget *parent = nullptr);~MapInput();const QList<ImageInfo> &getInputInfo(); // 获取下载地图所需的输入信息private:// ArcGisvoid 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); // 计算左上角瓦片Xint ltY = latTotile(lt.at(1).toDouble(), z); // 计算左上角瓦片Yint rdX = lonTotile(rd.at(0).toDouble(), z); // 计算右下角瓦片Xint rdY = latTotile(rd.at(1).toDouble(), z); // 计算右下角瓦片YImageInfo 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); // 计算左上角瓦片Xint ltY = latTotile(lt.at(1).toDouble(), z); // 计算左上角瓦片Yint rdX = lonTotile(rd.at(0).toDouble(), z); // 计算右下角瓦片Xint rdY = latTotile(rd.at(1).toDouble(), z); // 计算右下角瓦片YImageInfo 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-cnstatic 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=cnstatic 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"; #endifint 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); // 计算左上角瓦片Xint ltY = latTotile(lt.at(1).toDouble(), z); // 计算左上角瓦片Yint rdX = lonTotile(rd.at(0).toDouble(), z); // 计算右下角瓦片Xint rdY = latTotile(rd.at(1).toDouble(), z); // 计算右下角瓦片YQString 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); #endifm_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); // 设置最大线程数 #endifm_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
相关文章:

C++(Qt)-GIS开发-简易瓦片地图下载器
Qt-GIS开发-简易瓦片地图下载器 文章目录 Qt-GIS开发-简易瓦片地图下载器1、概述2、安装openssl3、实现效果4、主要代码4.1 算法函数4.2 瓦片地图下载url拼接4.3 多线程下载 5、源码地址6、参考 更多精彩内容👉个人内容分类汇总 👈👉GIS开发 …...

誉天教育7月开班计划:为梦想插上腾飞的翅膀!
随着夏日的脚步渐近,誉天教育也迎来了新一轮的学习热潮。在这个充满活力和希望的季节里,我们精心策划了7月的开班计划,旨在为广大学子提供一个优质、高效的学习平台,助力他们追逐梦想,实现自我价值。 本月 Linux云计算…...

STM32基础篇:GPIO
GPIO简介 GPIO:即General Purpose Input/Output,通用目的输入/输出。就是一种片上外设(内部模块)。 对于STM32的芯片来说,周围有一圈引脚,有时需要对引脚进行读写(读:从外部输入一…...
HTTPS 发送请求出现TLS握手失败
最近在工作中,调外部接口,发现在clientHello步骤报错,服务端没有返回serverHello。 从网上找了写方法,都没有解决; 在idea的vm options加上参数: -Djavax.net.debugSSL,handshake 把SSL和handshake的日…...

数字化精益生产系统--IFS财务管理系统
IFS财务管理系统是一款功能丰富、高效且灵活的企业财务管理软件,广泛应用于多个行业和不同规模的企业中。以下是对IFS财务管理系统的功能设计:...

基于SpringBoot的校园台球厅人员与设备管理系统
本系统是要设计一个校园台球厅人员与设备管理系统,这个系统能够满足校园台球厅人员与设备的管理及用户的校园台球厅人员与设备管理功能。系统的主要功能包括首页、个人中心、用户管理、会员账号管理、会员充值管理、球桌信息管理、会员预约管理、普通预约管理、留言…...

免杀笔记 ---> Session0--DLL注入
刚更新完上一篇,于是我们就马不停蹄的去跟新下一篇!! Session0注入 :: 各位看官如果觉得还不错的可以给博主点个赞💕💕 这次,我把这个脚本直接传到Github上了 喜欢的师傅点个Star噢…...

如何做好IT类的技术面试?
我们在找工作时,需要结合自己的现状,针对意向企业做好充分准备。作为程序员,你有哪些面试IT技术岗的技巧? 方向一:分享你面试IT公司的小技巧 我分享一些基于广泛观察和用户反馈的面试IT公司的小技巧: 技术准…...

A7 配置方式Master SPI如何更改位宽
在 FPGA 完成自初始化后,INIT 释放,FPGA 对模式引脚 (M[2:0]) 进行采样,以确定使用哪种配置模式。当模式引脚 M[2:0] 001 时,FPGA 开始以大约 3 MHz 的频率在 CCLK 上输出时钟。随后,FCS_B 驱动为低电平,紧…...
linux kthread任务管理
目录 一、linux 创建内核线程1.1 kthread_create1.2 kthread_create_worker kthread_queue_work 二、设置线程优先级和调度策略2.1 sched_setscheduler2.2 调度策略 一、linux 创建内核线程 1.1 kthread_create 在 linux 中,可以使用 kthread_create 接口创建内核…...

第一节 网络安全概述
一.网络空间安全 网络空间:一个由信息基础设施组成相互依赖的网络。 ---- 海陆空天(大海、陆 地、天空、航天) 通信保密阶段 ---- 计算机安全 ----- 信息系统安全 ----- 网络空间安全 计算机安全:开始秉持着“严于律己&#x…...

星光云VR全景系统源码
星光云VR全景系统源码 体验地址请查看...
spdlog一个非常好用的C++日志库(七): 源码分析之异常类spdlog_ex
目录 1.自定义异常类spdlog_ex 1.1.通用异常 1.2.系统调用异常 1.3.what()函数 2.异常的使用 2.1.抛出异常 2.2.控制异常使用 1.自定义异常类spdlog_ex 标准库异常类(std::exception)系列,能满足大多数使用异常的场景,但对…...

从一次 SQL 查询的全过程了解 DolphinDB 线程模型
1. 前言 DolphinDB 的线程模型较为复杂,写入与查询分布式表都可能需要多个类型的线程。通过了解 SQL 查询的全过程,可以帮助我们了解 DolphinDB 的线程模型,掌握 DolpinDB 的配置,以及优化系统性能的方法。 本教程以一个分布式 …...

Vue3.js“非原始值”响应式实现基本原理笔记(二)
如果您觉得这篇文章有帮助的话!给个点赞和评论支持下吧,感谢~ 作者:前端小王hs 阿里云社区博客专家/清华大学出版社签约作者/csdn百万访问前端博主/B站千粉前端up主 此篇文章是博主于2022年学习《Vue.js设计与实现》时的笔记整理而来 书籍&a…...

论文 | PRCA: 通过可插拔奖励驱动的上下文适配器拟合用于检索问答的黑盒大语言模型
论文全称:PRCA: Fitting Black-Box Large Language Models for Retrieval Question Answering via Pluggable Reward-Driven Contextual Adapter 核心问题:如何在检索增强式问答(ReQA)任务中,利用大型语言模型…...
网络状态的智能感知:WebKit 支持 Network Information API 深度解析
网络状态的智能感知:WebKit 支持 Network Information API 深度解析 在现代 Web 应用中,理解用户的网络连接状态对于提供适应性体验至关重要。Network Information API,一个新兴的 Web API,允许 Web 应用访问设备的网络信息&…...

Vue3基础知识:组合式API中的provide和inject,他们作用是什么?如何使用?以及案例演示
1.provide和inject相较于父子传递的不同在于provide,inject可以用于跨层级通信(通俗易懂的讲就是可以实现爷孙之间的直接信息传递)。 1.跨层级传递数据 1.在顶层组件通过provide函数提供数据 2.底层组件通过inject函数获取数据 演示一:跨…...
Transformer自注意力机制(Self-Attention)模型
上一篇我们介绍了transform专题一:Seq2seq model,也知道了transfrom属于seq2seq模型,这一排篇咱们接着介绍另外几种seq2seq架构的模型。)RNN(循环神经网络)CNN(卷积神经网络)&…...

【计算机体系结构】缓存的false sharing
在介绍缓存的false sharing之前,本文先介绍一下多核系统中缓存一致性是如何维护的。 目前主流的多核系统中的缓存一致性协议是MESI协议及其衍生协议。 MESI协议 MESI协议的4种状态 MESI协议有4种状态。MESI是4种状态的首字母缩写,缓存行的4种状态分别…...

手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...

练习(含atoi的模拟实现,自定义类型等练习)
一、结构体大小的计算及位段 (结构体大小计算及位段 详解请看:自定义类型:结构体进阶-CSDN博客) 1.在32位系统环境,编译选项为4字节对齐,那么sizeof(A)和sizeof(B)是多少? #pragma pack(4)st…...

《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)
CSI-2 协议详细解析 (一) 1. CSI-2层定义(CSI-2 Layer Definitions) 分层结构 :CSI-2协议分为6层: 物理层(PHY Layer) : 定义电气特性、时钟机制和传输介质(导线&#…...

UE5 学习系列(三)创建和移动物体
这篇博客是该系列的第三篇,是在之前两篇博客的基础上展开,主要介绍如何在操作界面中创建和拖动物体,这篇博客跟随的视频链接如下: B 站视频:s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…...
Neo4j 集群管理:原理、技术与最佳实践深度解析
Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...
Spring是如何解决Bean的循环依赖:三级缓存机制
1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间互相持有对方引用,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...

Rust 开发环境搭建
环境搭建 1、开发工具RustRover 或者vs code 2、Cygwin64 安装 https://cygwin.com/install.html 在工具终端执行: rustup toolchain install stable-x86_64-pc-windows-gnu rustup default stable-x86_64-pc-windows-gnu 2、Hello World fn main() { println…...

破解路内监管盲区:免布线低位视频桩重塑停车管理新标准
城市路内停车管理常因行道树遮挡、高位设备盲区等问题,导致车牌识别率低、逃费率高,传统模式在复杂路段束手无策。免布线低位视频桩凭借超低视角部署与智能算法,正成为破局关键。该设备安装于车位侧方0.5-0.7米高度,直接规避树枝遮…...

uniapp 小程序 学习(一)
利用Hbuilder 创建项目 运行到内置浏览器看效果 下载微信小程序 安装到Hbuilder 下载地址 :开发者工具默认安装 设置服务端口号 在Hbuilder中设置微信小程序 配置 找到运行设置,将微信开发者工具放入到Hbuilder中, 打开后出现 如下 bug 解…...

Linux 下 DMA 内存映射浅析
序 系统 I/O 设备驱动程序通常调用其特定子系统的接口为 DMA 分配内存,但最终会调到 DMA 子系统的dma_alloc_coherent()/dma_alloc_attrs() 等接口。 关于 dma_alloc_coherent 接口详细的代码讲解、调用流程,可以参考这篇文章,我觉得写的非常…...