C++(Qt)-GIS开发-QGraphicsView显示瓦片地图简单示例

C++(Qt)-GIS开发-QGraphicsView显示瓦片地图简单示例

文章目录

  • C++(Qt)-GIS开发-QGraphicsView显示瓦片地图简单示例
    • 1、概述
    • 2、实现效果
    • 3、主要代码
    • 4、源码地址

更多精彩内容
👉个人内容分类汇总 👈
👉GIS开发 👈

1、概述

  1. 支持多线程加载显示本地离线瓦片地图(墨卡托投影);
  2. 瓦片切片规则以左上角为原点(谷歌、高德、ArcGis等),不支持百度瓦片规则;
  3. 支持显示瓦片网格、编号信息。
  4. 支持鼠标滚轮缩放切换地图层级。
  5. 支持鼠标拖拽。
  6. 采用z/x/y层级瓦片存储格式。
  7. 在单文件中实现所有主要功能,简单便于理解。
  8. 以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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【十三】图解 Spring 核心数据结构:BeanDefinition 其二

图解 Spring 核心数据结构&#xff1a;BeanDefinition 其二 概述 前面写过一篇相关文章作为开篇介绍了一下BeanDefinition&#xff0c;本篇将深入细节来向读者展示BeanDefinition的设计&#xff0c;让我们一起来揭开日常开发中使用的bean的神秘面纱&#xff0c;深入细节透彻理解…

第9章 项目总结01:项目流程,每个模块的介绍

1 请介绍一下你的项目 学成在线项目是一个B2B2C的在线教育平台&#xff0c;本项目包括了用户端、机构端、运营端。 核心模块包括&#xff1a;内容管理、媒资管理、课程搜索、订单支付、选课管理、认证授权等。 下图是项目的功能模块图&#xff1a; 项目采用前后端分离的技…

去除gif动图背景的工具网站

选择视频或GIF - 取消屏幕 (unscreen.com)https://www.unscreen.com/upload

24-7-6-读书笔记(八)-《蒙田随笔集》[法]蒙田 [译]潘丽珍

文章目录 《蒙田随笔集》阅读笔记记录总结 《蒙田随笔集》 《蒙田随笔集》蒙田&#xff08;1533-1592&#xff09;&#xff0c;是个大神人&#xff0c;这本书就是250页的样子&#xff0c;但是却看了好长好长时间&#xff0c;体会还是挺深的&#xff0c;但看的也是不大仔细&…

【TORCH】绘制权重分布直方图,权重torch.fmod对torch.normal生成的随机数进行取模运算

要绘制上述代码中权重初始化的分布&#xff0c;可以分别展示每一层初始化权重的直方图。我们将用 torch.fmod 对 torch.normal 生成的随机数进行取模运算&#xff0c;确保权重值在 -2 到 2 之间。 含义解释 torch.normal(0, init_sd, size...)&#xff1a;生成服从均值为 0、…

以黑盒与白盒的角度分析和通关xss-labs(XSS漏洞类型与总结)

目录 目录 前言 XSS漏洞的总结和梳理 1.第一关(基础palyload) 黑盒测试 白盒测试 2.第二关(闭合) 黑盒测试 白盒测试 3.第三关(字符转义) 黑盒测试 白盒测试 4.第四关(字符过滤或替换) 黑盒测试 白盒测试 5.第五关(关键词替换) 黑盒测试 白盒测试 6.第六关(…

el-table实现固定列,及解决固定列导致部分滚动条无法拖动的问题

一、el-table实现固定列 当数据量动态变化时&#xff0c;可以为 Table 设置一个最大高度。 通过设置max-height属性为 Table 指定最大高度。此时若表格所需的高度大于最大高度&#xff0c;则会显示一个滚动条。 <div class"zn-filter-table"><!-- 表格--…

AI对于高考和IT行业的深远影响

目录 AI对IT行业的冲击及深远影响1. 工作自动化2. 新的就业机会3. 行业融合4. 技术升级和创新5. 数据的重要性 IT行业的冬天要持续多久&#xff1f;大学的软件开发类专业是否还值得报考&#xff1f;其他问题IT行业是否都是加班严重&#xff1f;35岁后就业困难是否普遍现象&…

基于TCP的在线词典系统(分阶段实现)

1.功能说明 一共四个功能&#xff1a; 注册 登录 查询单词 查询历史记录 单词和解释保存在文件中&#xff0c;单词和解释只占一行, 一行最多300个字节&#xff0c;单词和解释之间至少有一个空格。 2.功能演示 3、分阶段完成各个功能 3.1 完成服务器和客户端的连接 servic…

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>…

【MySQL】事务四大特性以及实现原理

事务四大特性 原子性&#xff08;Atomicity&#xff09; 事务中的所有操作要么全部完成&#xff0c;要么全部不执行。如果事务中的任何一步失败&#xff0c;整个事务都会被回滚&#xff0c;以保持数据的完整性。 一致性&#xff08;Consistency&#xff09; 事务应确保数据库…

机器学习——决策树及其可视化

1、决策树概念 顾名思义&#xff0c;决策树是利用数据结构中树结构来进行判断&#xff0c;每一个结点相当于一个判断条件&#xff0c;叶子结点即是最终的类别。以鸢尾花为例&#xff0c;可以得到如下的决策树&#xff1a; 2、决策树分类的依据是什么&#xff1f; 根据前面分…

跨越语言的界限:Vue I18n 国际化指南

前言 &#x1f4eb; 大家好&#xff0c;我是南木元元&#xff0c;热爱技术和分享&#xff0c;欢迎大家交流&#xff0c;一起学习进步&#xff01; &#x1f345; 个人主页&#xff1a;南木元元 目录 国际化简介 vue-i18n 安装和配置 创建语言包 基本使用 切换语言 动态翻…

CTFShow的RE题(二)

逆向5 附件无后缀&#xff0c;查一下是zip&#xff0c;解压得到一个exe一个dll文件。 往下继续看 但也根进去看看 发现是在加载的dll文件 还有一个返回时调用的函数 发现是打印函数 根据以往的经验应该是要跳转到这里&#xff0c;动调一下。 发现exe链接了dll&#xff0c;…

Lock4j简单的支持不同方案的高性能分布式锁实现及源码解析

文章目录 1.Lock4j是什么?1.1简介1.2项目地址1.3 我之前手写的分布式锁和限流的实现 2.特性3.如何使用3.1引入相关依赖3.2 配置redis或zookeeper3.3 使用方式3.3.1 注解式自动式3.3.2 手动式 4.源码解析4.1项目目录4.2实现思路 5.总结 1.Lock4j是什么? 1.1简介 lock4j是苞米…

昇思第6天

函数式自动微分 神经网络的训练主要使用反向传播算法&#xff0c;模型预测值&#xff08;logits&#xff09;与正确标签&#xff08;label&#xff09;送入损失函数&#xff08;loss function&#xff09;获得loss&#xff0c;然后进行反向传播计算&#xff0c;求得梯度&#…

【算法专题】双指针算法

1. 移动零 题目分析 对于这类数组分块的问题&#xff0c;我们应该首先想到用双指针的思路来进行处理&#xff0c;因为数组可以通过下标进行访问&#xff0c;所以说我们不用真的定义指针&#xff0c;用下标即可。比如本题就要求将数组划分为零区域和非零区域&#xff0c;我们不…

时序分析基本概念介绍——SI/crosstalk/delta delay/noise/timing Window

文章目录 前言一、Crosstalk1. Crosstalk Delay Effects2. Crosstalk Noise Effects 二、Crosstalk Analysis1. Crosstalk Delay Analysis2. Crosstalk Noise Analysis 三、如何 fix delta delay 和 noise violations1. 检查delta delay 和 noisedelta delay checknoise check …

【C语言小知识】缓冲区

缓冲区 当我们使用printf()将数据显示在屏幕上&#xff0c;或者使用scanf()函数将数据输入到电脑里&#xff0c;我们是否会产生些许疑问&#xff1f;为何输入的字符会直接显示到屏幕上等等。这里需要介绍一个C语言中的一个关键概念——缓冲区。 当我们使用老式系统进行运行代码…

suricata7 rule加载(一)加载 action

suricata7.0.5 一、前提条件 1.1 关键字注册 main | --> SuricataMain|--> PostConfLoadedSetup|--> SigTableSetupsigmatch_table是一个全局数组&#xff0c;每个元素就是一个关键字节点&#xff0c;是对关键字如何处理等相关回调函数。非常重要的一个结构&#x…