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;     // 存放地图图元组的数组,以瓦片层级为key
        QGraphicsItemGroup* m_mapitemGroup = nullptr;        // 当前显示层级图元
        QHash<int, QGraphicsItemGroup*> m_gridItemGroups;    // 存放地图网格图元组的数组,以瓦片层级为key
        QGraphicsItemGroup* 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);   // 去除后缀,以文件名为y
                    if(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);                           // 设置字体大小为12
        QFontMetrics 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.mfbz.cn/a/778140.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

系统安全与应用

目录 1. 系统账户清理 2. 密码安全性控制 2.1 密码复杂性 2.2 密码时限 3 命令历史查看限制 4. 终端自动注销 5. su权限以及sudo提权 5.1 su权限 5.2 sudo提权 6. 限制更改GRUB引导 7. 网络端口扫描 那天不知道为什么&#xff0c;心血来潮看了一下passwd配置文件&am…

在 PostgreSQL 中,如何处理大规模的文本数据以提高查询性能?

文章目录 一、引言二、理解 PostgreSQL 中的文本数据类型三、数据建模策略四、索引选择与优化五、查询优化技巧六、示例场景与性能对比七、分区表八、数据压缩九、定期维护十、总结 在 PostgreSQL 中处理大规模文本数据以提高查询性能 一、引言 在当今的数据驱动的世界中&…

Android 集成OpenCV

记录自己在学习使用OpenCV的过程 我使用的是4.10.0 版本 Android 集成OpenCV 步骤 下载OpenCV新建工程依赖OpenCV初始化及逻辑处理 1、下载OpenCV 并解压到自己的电脑 官网 地址&#xff1a;https://opencv.org/releases/ 个人地址&#xff1a;https://pan.baidu.com/s/19f…

前端必修技能:高手进阶核心知识分享 - CSS mix-blend-mode 图片混合模式详解

标签定义及使用说明 mix-blend-mode 属性描述了元素的内容应该与元素的直系父元素的内容和元素的背景如何混合。 语法 mix-blend-mod: 使用mix-blend-mode 各种混合模式实例 注意: Internet Explorer 或 Edge 浏览器不支持 mix-blend-mode 属性。 &#xff08;还是那个熟…

收银系统源码-千呼新零售2.0

千呼新零售2.0系统是零售行业连锁店一体化收银系统&#xff0c;包括线下收银线上商城连锁店管理ERP管理商品管理供应商管理会员营销等功能为一体&#xff0c;线上线下数据全部打通。 适用于商超、便利店、水果、生鲜、母婴、服装、零食、百货、宠物等连锁店使用。 详细介绍请…

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

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

【Oracle】Oracle常用函数

目录 聚合函数数字函数1. ABS函数&#xff1a;返回一个数的绝对值。2. CEIL函数&#xff1a;返回大于等于给定数的最小整数。3. FLOOR函数&#xff1a;返回小于等于给定数的最大整数。4. ROUND函数&#xff1a;将一个数四舍五入到指定的小数位。5. MOD函数&#xff1a;返回两个…

Ubuntu固定虚拟机的ip地址

1、由于虚拟机网络是桥接&#xff0c;所以ip地址会不停地变化&#xff0c;接下来我们就讲述ip如何固定 2、如果apt安装时报错W: Target CNF (multiverse/cnf/Commands-all) is configured multiple times in /etc/apt/sources.list:10&#xff0c; 检查 /etc/apt/sources.list…

SpringBoot新手快速入门系列教程二:MySql5.7.44的免安装版本下载和配置,以及简单的Mysql生存指令指南。

我们要如何选择MySql 目前主流的Mysql有5.0、8.0、9.0 主要区别 MySQL 5.0 发布年份&#xff1a;2005年特性&#xff1a; 基础事务支持存储过程、触发器、视图基础存储引擎&#xff08;如MyISAM、InnoDB&#xff09;外键支持基本的全文搜索性能和扩展性&#xff1a; 相对较…

HTML+CSS+JavaScript入门学习

目录 1. 前言2. HTML2.1 HTML简介2.2 HTML标签 3. CSS3.1 CSS知识整理及总结3.2 CSS之flex布局 4. JavaScript4.1 JavaScript知识整理及总结1-基础篇4.2 JavaScript知识整理及总结2-进阶篇 1. 前言 本文主要采用转载的形式&#xff0c;偶尔发现了一个比较不错的博客站点&#…

华为ENSP防火墙+路由器+交换机的常规配置

(防火墙区域DHCP基于接口DHCP中继服务器区域有线区域无线区域&#xff09;配置 一、适用场景&#xff1a; 1、普通企业级网络无冗余网络环境&#xff0c;防火墙作为边界安全设备&#xff0c;分trust&#xff08;内部网络信任区域&#xff09;、untrust&#xff08;外部网络非信…

计算机网络-IP组播基础

一、概述 在前面的学习交换机和路由协议&#xff0c;二层通信是数据链路层间通信&#xff0c;在同一个广播域间通过源MAC地址和目的MAC地址进行通信&#xff0c;当两台主机第一次通信由于不清楚目的MAC地址需要进行广播泛洪&#xff0c;目的主机回复自身MAC地址&#xff0c;然后…

JSP WEB开发(一) JSP语言基础

目录 JSP JSP简介&#xff1a; JSP页面 JSP运行原理 JSP脚本元素 JAVA程序片 局部变量 全局变量和方法的声明 全局变量 方法的声明 程序片执行特点 synchronized关键字 表达式 JSP指令标记 page指令 include指令 JSP动作标记 JSP动作元素include和include指令的…

【C++】B树及其实现

写目录 一、B树的基本概念1.引入2.B树的概念 二、B树的实现1.B树的定义2.B树的查找3.B树的插入操作4.B树的删除5.B树的遍历6.B树的高度7.整体代码 三、B树和B*树1.B树2.B*树3.总结 一、B树的基本概念 1.引入 我们已经学习过二叉排序树、AVL树和红黑树三种树形查找结构&#x…

1-3 NLP为什么这么难做

1-3 NLP为什么这么难做 主目录点这里 字词结构的复杂性 中文以汉字为基础单位&#xff0c;一个词通常由一个或多个汉字组成&#xff0c;而不像英语词汇单元由字母构成。这使得中文分词&#xff08;切分句子为词语&#xff09;成为一个具有挑战性的任务。语言歧义性 中文中常…

Mysql-常见DML-DQL-语句语法用法总结

1、常见DML语句 1.1 INSERT语句 说明&#xff1a;将数据插入到数据库表中。 INSERT INTO table_name (column1, column2, ...) VALUES (value1, value2, ...); 实例&#xff1a;添加C罗信息到数据库表中 insert into employee (ID, name, gender, entrydate, age) values …

eclipse断点调试(用图说话)

eclipse断点调试&#xff08;用图说话&#xff09; debug方式启动项目&#xff0c;后端调试bug调试 前端代码调试&#xff0c;请参考浏览器断点调试&#xff08;用图说话&#xff09; 1、前端 选中一条数据&#xff0c;点击删除按钮 2、后端接口打断点 断点按钮 介绍 resu…

python如何设计窗口

PyQt是一个基于Qt的接口包&#xff0c;可以直接拖拽控件设计UI界面&#xff0c;下面我简单介绍一下这个包的安装和使用&#xff0c;感兴趣的朋友可以自己尝试一下&#xff1a; 1、首先&#xff0c;安装PyQt模块&#xff0c;这个直接在cmd窗口输入命令“pip install pyqt5”就行…

Hugging Face 全球政策负责人首次参加WAIC 2024 前沿 AI 安全和治理论坛

Hugging Face 全球政策负责人艾琳-索莱曼 &#xff08; Irene Solaiman &#xff09;将参加7月5日在上海举办的WAIC-前沿人工智能安全和治理论坛&#xff0c;并在现场进行主旨演讲和参加圆桌讨论。具体时间信息如下&#xff1a;主旨演讲&#xff1a;开源治理的国际影响时间 &am…

YOLOv8改进 添加轻量级注意力机制ELAttention

一、ELA论文 论文地址:2403.01123 (arxiv.org) 二、Efficient Local Attention结构 ELA (Efficient Local Attention) 被用于处理自然语言处理任务中的序列数据。它旨在提高传统注意力机制的效率,并减少其计算和存储成本。 在传统的注意力机制中,计算每个输入位置与所有其…