C++(Qt)-GIS开发-QGraphicsView显示瓦片地图简单示例
C++(Qt)-GIS开发-QGraphicsView显示瓦片地图简单示例
文章目录
- C++(Qt)-GIS开发-QGraphicsView显示瓦片地图简单示例
- 1、概述
- 2、实现效果
- 3、主要代码
- 4、源码地址
| 更多精彩内容 |
|---|
| 👉个人内容分类汇总 👈 |
| 👉GIS开发 👈 |
1、概述
- 支持多线程加载显示本地离线瓦片地图(墨卡托投影);
- 瓦片切片规则以左上角为原点(谷歌、高德、ArcGis等),不支持百度瓦片规则;
- 支持显示瓦片网格、编号信息。
- 支持鼠标滚轮缩放切换地图层级。
- 支持鼠标拖拽。
- 采用z/x/y层级瓦片存储格式。
- 在单文件中实现所有主要功能,简单便于理解。
- 以QGraphicsView原点为起始位置,将加载的第一张瓦片显示在原点,其它瓦片相对于第一张瓦片进行显示【相对像素坐标】。
开发环境说明
- 系统:Windows11、Ubuntu20.04
- Qt版本:Qt 5.14.2
- 编译器:MSVC2017-64、GCC/G++64
2、实现效果
使用瓦片地图工具下载z/x/y存储格式的瓦片地图进行显示。

3、主要代码
-
mapgraphicsview.h文件
#ifndef MAPGRAPHICSVIEW_H #define MAPGRAPHICSVIEW_H#include <QGraphicsView> #include <QGraphicsScene> #include <QFuture>class MapGraphicsView : public QGraphicsView {Q_OBJECT public:explicit MapGraphicsView(QWidget *parent = nullptr);~MapGraphicsView();void setPath(const QString& path);void quit();protected:void wheelEvent(QWheelEvent *event) override;signals:void addImage(QPixmap img, QPoint pos); private:void getMapLevel(); // 获取路径中瓦片地图的层级void getTitle(); // 获取路径中瓦片地图编号void loatImage(); // 加载瓦片void clearReset(); // 清除重置所有内容int getKey(); // 获取当前显示的层级key值void on_addImage(QPixmap img, QPoint pos);private:QGraphicsScene* m_scene = nullptr;QString m_path; // 瓦片地图文件路径QHash<int, QGraphicsItemGroup*> m_mapItemGroups; // 存放地图图元组的数组,以瓦片层级为keyQGraphicsItemGroup* m_mapitemGroup = nullptr; // 当前显示层级图元QHash<int, QGraphicsItemGroup*> m_gridItemGroups; // 存放地图网格图元组的数组,以瓦片层级为keyQGraphicsItemGroup* m_griditemGroup = nullptr; // 当前显示层级网格图元int m_keyIndex = 0; // 当前显示的瓦片层级QVector<QPoint> m_imgTitle; // 保存图片编号QFuture<void> m_future; };#endif // MAPGRAPHICSVIEW_H -
mapgraphicsview.cpp文件
#include "mapgraphicsview.h"#include <QDir> #include <QDebug> #include <QGraphicsItemGroup> #include <QtConcurrent> #include <QWheelEvent>MapGraphicsView* g_this = nullptr; MapGraphicsView::MapGraphicsView(QWidget *parent) : QGraphicsView(parent) {m_scene = new QGraphicsScene(this);this->setScene(m_scene);g_this = this;connect(this, &MapGraphicsView::addImage, this, &MapGraphicsView::on_addImage);this->setDragMode(QGraphicsView::ScrollHandDrag); // 设置鼠标拖拽 // QThreadPool::globalInstance()->setMaxThreadCount(1); // 可以设置线程池线程数 }MapGraphicsView::~MapGraphicsView() {g_this = nullptr;quit(); // 如果程序退出时还在调用map就会报错,所以需要关闭 }/*** @brief 退出多线程*/ void MapGraphicsView::quit() {if(m_future.isRunning()) // 判断是否在运行{m_future.cancel(); // 取消多线程m_future.waitForFinished(); // 等待退出} }/*** @brief 设置加载显示的瓦片地图路径* @param path*/ void MapGraphicsView::setPath(const QString &path) {if(path.isEmpty()) return;m_path = path;getMapLevel(); // 获取瓦片层级loatImage(); // 加载第一层瓦片 }/*** @brief 鼠标缩放地图* @param event*/ void MapGraphicsView::wheelEvent(QWheelEvent *event) {QGraphicsView::wheelEvent(event);if(m_future.isRunning()) // 判断是否在运行{return;}if(event->angleDelta().y() > 0) // 放大{if(m_keyIndex < m_mapItemGroups.count() -1){m_keyIndex++;}}else{if(m_keyIndex > 0){m_keyIndex--;}}loatImage(); // 加载新的层级瓦片 }/*** @brief 计算瓦片层级*/ void MapGraphicsView::getMapLevel() {if(m_path.isEmpty()) return;clearReset(); // 加载新瓦片路径时将之前的内容清空QDir dir(m_path);dir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot); // 设置过滤类型为文件夹,且不包含隐藏文件夹dir.setSorting(QDir::Name); // 设置按文件夹名称排序QStringList dirs = dir.entryList();for(auto& strDir : dirs){bool ok;int level = strDir.toInt(&ok);if(ok){if(!m_mapItemGroups.contains(level)) // 如果不包含{// 初始化加载所有瓦片层级到场景中,默认不显示QGraphicsItemGroup* itemMap = new QGraphicsItemGroup();m_scene->addItem(itemMap);itemMap->setVisible(false);m_mapItemGroups[level] = itemMap;// 初始化加载所有瓦片层级网格到场景中,默认不显示QGraphicsItemGroup* itemGrid = new QGraphicsItemGroup();m_scene->addItem(itemGrid);itemGrid->setVisible(false);m_gridItemGroups[level] = itemGrid;}}} }/*** @brief 获取当前显示层级中所有瓦片的编号*/ void MapGraphicsView::getTitle() {QString path = m_path + QString("/%1").arg(getKey()); // z 第一层文件夹QDir dir(path);dir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot); // 设置过滤类型为文件夹,且不包含隐藏文件夹dir.setSorting(QDir::Name); // 设置按文件夹名称排序QStringList dirs = dir.entryList();QPoint point;for(auto& strDir : dirs){bool ok;int x = strDir.toInt(&ok); // x层级 第二层文件夹if(ok){point.setX(x);dir.setPath(path + QString("/%1").arg(x));dir.setFilter(QDir::Files | QDir::NoDotAndDotDot); // 设置过滤类型为文件,且不包含隐藏文件dir.setSorting(QDir::Name); // 设置按文件夹名称排序QStringList files = dir.entryList();for(auto& file: files){int y = file.split('.').at(0).toInt(&ok); // 去除后缀,以文件名为yif(ok){point.setY(y);m_imgTitle.append(point);}}}} }QString g_path; // 保存当前层级路径 /*** @brief 在多线程中加载图片* @param point*/ void readImg(const QPoint& point) {QString path = QString("%1/%2/%3.jpg").arg(g_path).arg(point.x()).arg(point.y());QPixmap image;if(image.load(path)){if(g_this){emit g_this->addImage(image, point); // 由于不能在子线程中访问ui,所以这里通过信号将图片传递到ui线程进行绘制}} // QThread::msleep(50); // 加载时加上延时可以更加清晰的看到加载过程 }/*** @brief 加载显示瓦片图元*/ void MapGraphicsView::loatImage() {quit(); // 加载新瓦片之前判断是否还有线程在运行m_imgTitle.clear();if(m_mapitemGroup){m_mapitemGroup->setVisible(false); // 隐藏图层m_griditemGroup->setVisible(false); // 隐藏图层}m_mapitemGroup = m_mapItemGroups.value(getKey());m_griditemGroup = m_gridItemGroups.value(getKey());if(!m_mapitemGroup || !m_griditemGroup) return;if(m_mapitemGroup->boundingRect().isEmpty()) // 如果图元为空则加载图元显示{getTitle(); // 获取新层级的所有瓦片编号g_path = m_path + QString("/%1").arg(getKey());m_future = QtConcurrent::map(m_imgTitle, readImg);}m_mapitemGroup->setVisible(true); // 显示新瓦片图层m_griditemGroup->setVisible(true); // 显示新网格图层m_scene->setSceneRect(m_mapitemGroup->boundingRect()); // 根据图元大小自适应调整场景大小 }/*** @brief 清除重置所有内容*/ void MapGraphicsView::clearReset() {if(m_mapItemGroups.isEmpty()) return;m_keyIndex = 0;m_mapitemGroup = nullptr;m_griditemGroup = nullptr;m_imgTitle.clear();QList<int>keys = m_mapItemGroups.keys();for(auto key : keys){// 清除瓦片图元QGraphicsItemGroup* item = m_mapItemGroups.value(key);m_scene->removeItem(item); // 从场景中移除图元delete item;m_mapItemGroups.remove(key); // 从哈希表中移除图元// 清除网格item = m_gridItemGroups.value(key);m_scene->removeItem(item); // 从场景中移除图元delete item;m_gridItemGroups.remove(key); // 从哈希表中移除图元} }/*** @brief 获取当前层级的key值* @return 返回-1表示不存在*/ int MapGraphicsView::getKey() {if(m_mapItemGroups.isEmpty()) return -1;QList<int>keys = m_mapItemGroups.keys();std::sort(keys.begin(), keys.end()); // 由于keys不是升序的,所以需要进行排序if(m_keyIndex < 0 || m_keyIndex >= keys.count()){return -1;}return keys.at(m_keyIndex); }/*** @brief 绘制地图瓦片图元* @param img 显示的图片* @param pos 图片显示的位置*/ void MapGraphicsView::on_addImage(QPixmap img, QPoint pos) {if(!m_mapitemGroup || m_imgTitle.isEmpty()){return;}// 计算瓦片显示位置,默认为256*256的瓦片大小QPoint& begin = m_imgTitle.first();int x = (pos.x() - begin.x()) * 256;int y = (pos.y() - begin.y()) * 256;// 绘制瓦片QGraphicsPixmapItem* itemImg = new QGraphicsPixmapItem(img);itemImg->setPos(x, y); // 以第一张瓦片为原点m_mapitemGroup->addToGroup(itemImg);// 绘制网格、QGraphicsRectItem* itemRect = new QGraphicsRectItem(x, y, 256, 256);m_griditemGroup->addToGroup(itemRect);itemRect->setPen(QPen(Qt::red));// 绘制编号QString text = QString("%1,%2,%3").arg(pos.x()).arg(pos.y()).arg(getKey());QGraphicsSimpleTextItem* itemText = new QGraphicsSimpleTextItem(text);QFont font;font.setPointSize(14); // 设置字体大小为12QFontMetrics metrics(font);qreal w = metrics.horizontalAdvance(text) / 2.0; // 计算字符串宽度qreal h = metrics.height() / 2.0; // 字符串高度itemText->setPos(x + 128 - w, y + 128 - h); // 编号居中显示itemText->setFont(font);itemText->setPen(QPen(Qt::red));m_griditemGroup->addToGroup(itemText);m_scene->setSceneRect(m_mapitemGroup->boundingRect()); // 根据图元大小自适应调整场景大小 }
4、源码地址
- github
- gitee
相关文章:
C++(Qt)-GIS开发-QGraphicsView显示瓦片地图简单示例
C(Qt)-GIS开发-QGraphicsView显示瓦片地图简单示例 文章目录 C(Qt)-GIS开发-QGraphicsView显示瓦片地图简单示例1、概述2、实现效果3、主要代码4、源码地址 更多精彩内容👉个人内容分类汇总 👈👉GIS开发 👈 1、概述 支持多线程加…...
CTFShow的RE题(三)
数学不及格 strtol 函数 long strtol(char str, char **endptr, int base); 将字符串转换为长整型 就是解这个方程组了 主要就是 v4, v9的关系, 3v9-(v10v11v12)62d10d4673 v4 v12 v11 v10 0x13A31412F8C 得到 3*v9v419D024E75FF(1773860189695) 重点&…...
WordPress主题开发进群付费主题v1.1.2 多种引流方式
全新前端UI界面,多种前端交互特效让页面不再单调,进群页面群成员数,群成员头像名称,每次刷新页面随机更新不重复,最下面评论和点赞也是如此随机刷新不重复 进群页面简介,群聊名称,群内展示&…...
SAP中的 UPDATA TASK 和 BACKGROUND TASK
前言: 记录这篇文章起因是调查生产订单报工问题引申出来的一个问题,后来再次调查后了解了其中缘由,大概记录以下,如有不对,欢迎指正。问题原贴如下: SAP CO11N BAPI_PRODORDCONF_CREATE_TT连续报工异步更…...
UDP协议:独特之处及其在网络通信中的应用
在网络通信领域,UDP(用户数据报协议,User Datagram Protocol)是一种广泛使用的传输层协议。与TCP(传输控制协议,Transmission Control Protocol)相比,UDP具有其独特的特点和适用场景…...
支持向量机(Support Vector Machine,SVM)及Python和MATLAB实现
支持向量机(Support Vector Machine,SVM)是一种经典的机器学习算法,广泛应用于模式识别、数据分类和回归分析等领域。SVM的背景可以追溯到1990s年代,由Vladimir Vapnik等人提出,并在之后不断发展和完善。 …...
【RT-thread studio 下使用STM32F103-学习sem-信号量-初步使用-线程之间控制-基础样例】
【RT-thread studio 下使用STM32F103-学习sem-信号量-初步使用-线程之间控制-基础样例】 1、前言2、环境3、事项了解(1)了解sem概念-了解官网消息(2)根据自己理解,设计几个使用方式(3)不建议运行…...
使用nodejs输出著作权申请所需的word版源码
使用nodejs输出著作权申请所需的word版源码 背景 软件著作权申请需要提供一份80页的word版源代码,如果手工复制源码到word文档中,工作量将无聊到让任何一个DAO人员血压爆表,因此我们不得不编写一个简单的文本处理代码,通过自动方…...
[Vite]vite-plugin-react和vite-plugin-react-swc插件原理了解
[Vite]vite-plugin-react和vite-plugin-react-swc插件原理了解 共同的作用 JSX 支持:插件为 React 应用程序中的 JSX 语法提供支持,确保它可以被正确地转换为 JavaScript。Fast Refresh:提供热更新功能,当应用程序在开发服务器上…...
记一次使用“try-with-resources“的语法导致的BUG
背景描述 最近使用try-catch的时候遇到了一个问题,背景是这样的:当第一次与数据库建立连接以后执行查询完毕并没有手动关闭连接,但是当我第二次获取连接的时候报错了,显示数据库连接失败,连接已经关闭。 org.postgres…...
用Excel处理数据图像,出现交叉怎么办?
一、问题描述 用excel制作X-Y散点图,意外的出现了4个交叉点,而实际上的图表数据是没有交叉的。 二、模拟图表 模拟部分数据,并创建X-Y散点图,数据区域,X轴数据是依次增加的,因此散点图应该是没有交叉的。…...
SpringBoot | 大新闻项目后端(redis优化登录)
该项目的前篇内容的使用jwt令牌实现登录认证,使用Md5加密实现注册,在上一篇:http://t.csdnimg.cn/vn3rB 该篇主要内容:redis优化登录和ThreadLocal提供线程局部变量,以及该大新闻项目的主要代码。 redis优化登录 其实…...
ESP32——物联网小项目汇总
商品级ESP32智能手表 [文章链接] 用ESP32,做了个siri?!开源了! [文章链接]...
flutter:监听路由的变化
问题 当从路由B页面返回路由A页面后,A页面需要进行数据刷新。因此需要监听路由变化 解决 使用RouteObserver进行录音监听 创建全局变量,不在任何类中 final RouteObserver<PageRoute> routeObserver RouteObserver<PageRoute>();在mai…...
Linux多进程和多线程(六)进程间通信-共享内存
多进程(六) 共享内存共享内存的创建 示例: 共享内存删除 共享内存映射 共享内存映射的创建解除共享内存映射示例:写入和读取共享内存中的数据 写入: ### 读取: 大致操作流程: 多进程(六) 共享内存 共享内存是将分配的物理空间直接映射到进程的⽤户虚拟地址空间中, 减少数据在…...
ruoyi后台修改
一、日志文件过大分包 \ruoyi-admin\src\main\resources\logback.xml <!-- 系统日志输出 --> <appender name"file_info" class"ch.qos.logback.core.rolling.RollingFileAppender"><file>${log.path}/sys-info.log</file><!…...
macOS查看系统日志的方法
1、command空格键打开搜索框,输入‘控制台’并打开 2、选择日志报告,根据日期打开自己需要的文件就可以...
数字信号处理及MATLAB仿真(3)——采样与量化
今天写主要来编的程序就是咱们AD变换的两个步骤。一个是采样,还有一个是量化。大家可以先看看,这一过程当中的信号是如何变化的。信号的变换图如下。 先说说采样,采样是将连续时间信号转换为离散时间信号的过程。在采样过程中,连续…...
云端AI大模型群体智慧后台架构思考
1 大模型的调研 1.1 主流的大模型 openai-chatgpt 阿里巴巴-通义千问 一个专门响应人类指令的大模型。我是效率助手,也是点子生成机,我服务于人类,致力于让生活更美好。 百度-文心一言(千帆大模型) 文心一言"…...
算法系列--分治排序|再谈快速排序|快速排序的优化|快速选择算法
前言:本文就前期学习快速排序算法的一些疑惑点进行详细解答,并且给出基础快速排序算法的优化版本 一.再谈快速排序 快速排序算法的核心是分治思想,分治策略分为以下三步: 分解:将原问题分解为若干相似,规模较小的子问题解决:如果子问题规模较小,直接解决;否则递归解决子问题合…...
从新手到专家:OpenCore配置工具OCAT的实战应用指南
从新手到专家:OpenCore配置工具OCAT的实战应用指南 【免费下载链接】OCAuxiliaryTools Cross-platform GUI management tools for OpenCore(OCAT) 项目地址: https://gitcode.com/gh_mirrors/oc/OCAuxiliaryTools 如果你正在为黑苹果系…...
模拟地和数字地到底怎么接?从ADC设计误区讲起,用磁珠还是直接铺铜?
数模混合电路设计中的地平面处理:从ADC噪声抑制到系统级EMC优化 1. 数模混合电路的接地困局:当磁珠成为噪声放大器 在24位ADC采样电路中,工程师老张遇到了一个诡异现象:当输入信号低于1mV时,采集数据会出现周期性毛刺。…...
如何用QuickRecorder解决macOS录屏痛点:高效专业的从入门到精通实践指南
如何用QuickRecorder解决macOS录屏痛点:高效专业的从入门到精通实践指南 【免费下载链接】QuickRecorder A lightweight screen recorder based on ScreenCapture Kit for macOS / 基于 ScreenCapture Kit 的轻量化多功能 macOS 录屏工具 项目地址: https://gitco…...
Outfit字体全攻略:5大核心优势与零基础实战指南
Outfit字体全攻略:5大核心优势与零基础实战指南 【免费下载链接】Outfit-Fonts The most on-brand typeface 项目地址: https://gitcode.com/gh_mirrors/ou/Outfit-Fonts Outfit字体作为一款专业的开源无衬线字体,凭借其完整的9种字重体系和现代设…...
Qwen3.5-4B-Claude-Opus行业落地:高校编程教学辅助与算法解题思路生成
Qwen3.5-4B-Claude-Opus行业落地:高校编程教学辅助与算法解题思路生成 1. 模型介绍与教育场景适配性 Qwen3.5-4B-Claude-4.6-Opus-Reasoning-Distilled-GGUF是一个专为推理任务优化的轻量级AI模型,特别适合教育领域的应用场景。该模型基于Qwen3.5-4B架…...
如何快速掌握扩散模型:PyTorch实现的终极指南
如何快速掌握扩散模型:PyTorch实现的终极指南 【免费下载链接】Diffusion-Models-pytorch Pytorch implementation of Diffusion Models (https://arxiv.org/pdf/2006.11239.pdf) 项目地址: https://gitcode.com/gh_mirrors/di/Diffusion-Models-pytorch 想要…...
别再被‘小样本’难倒了!用Python的PyMC3库实战层次贝叶斯模型
用PyMC3解锁小样本分析:层次贝叶斯建模实战指南 当你的数据集像便利店冰柜里的酸奶——每个品类只有零星几瓶时,传统统计方法往往会束手无策。想象你正分析20个城市的新开门店周销售额,每个城市却只有3-5条数据记录。这时,层次贝叶…...
安卓玩机神器:无需Root的“搞机工具箱”全功能解析与实战指南
1. 安卓玩机新选择:搞机工具箱为何成为神器? 最近在折腾安卓手机时,发现了一个宝藏工具——搞机工具箱。作为一个长期和安卓系统打交道的玩家,我试过各种需要Root权限的工具,但这款软件最让我惊喜的是它完全不需要Root…...
DoL-Lyra构建系统:5分钟学会自动化游戏MOD打包
DoL-Lyra构建系统:5分钟学会自动化游戏MOD打包 【免费下载链接】DOL-CHS-MODS Degrees of Lewdity 整合 项目地址: https://gitcode.com/gh_mirrors/do/DOL-CHS-MODS DOL-CHS-MODS(Degrees of Lewdity汉化美化整合包)是一款专为Degree…...
PVB于EVA胶片的区别
PVB于EVA胶片的区别实例:PVB用于封装“双玻璃光伏组件”:玻璃+PVB+电池片+PVB+玻璃,PVB胶片已取代EVA胶片。为什么用PVB,不像我们现在一样用EVA?因为: 在玻璃…...
