C++(Qt)-GIS开发-QGraphicsView显示在线瓦片地图

C++(Qt)-GIS开发-QGraphicsView显示在线瓦片地图

文章目录

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

1、概述

  1. 支持加载显示在线瓦片地图(墨卡托投影);
  2. 瓦片切片规则以左上角为原点(谷歌、高德、ArcGis等),不支持百度瓦片规则;
  3. 支持显示瓦片网格、编号信息。
  4. 支持鼠标滚轮缩放切换地图层级。
  5. 支持鼠标拖拽。
  6. 支持显示瓦片编号、瓦片网格;
  7. 支持在线程池中快速下载在线瓦片;
  8. 以北纬85.05,西经-180为坐标原点【绝对像素坐标】。
  9. 默认支持下载显示多格式高德、Bing、ArcGis瓦片地图。
  10. 支持x/y/z、x/z/y、z/y/x任意顺序格式、quadKey格式的url。

开发环境说明

  • 系统:Windows11、Ubuntu20.04
  • Qt版本:Qt 5.14.2
  • 编译器:MSVC2017-64、GCC/G++64

2、实现效果

3、主要代码

  • geturl.h

    cpp 复制代码
    #ifndef GETURL_H
    #define GETURL_H
    
    #include "mapStruct.h"
    #include <qfuture.h>
    #include <qset.h>
    #include <QObject>
    
    class GetUrlInterface : public QObject
    {
        Q_OBJECT
    public:
        static GetUrlInterface* getInterface()
        {
            static GetUrlInterface interface;
            return &interface;
        }
    
    signals:
        void update(ImageInfo info);             // 传出下载的瓦片图信息
        void updateTitle(int x, int y, int z);   // 传出下载的瓦片编号
    
        void showRect(QRect rect);   // 设置显示像素范围
        void setLevel(int level);    // 设置瓦片层级
    };
    
    class GetUrl : public QObject
    {
        Q_OBJECT
    public:
        explicit GetUrl(QObject* parent = nullptr);
        ~GetUrl();
    
        void setUrl(QString url);   // 设置获取瓦片地图的源地址
        void getImg(QRect rect, int level);
        void showRect(QRect rect);
        void setLevel(int level);   // 设置瓦片层级
    
    private:
        void getTitle(QRect rect, int level);    // 获取所有需要下载的瓦片地图编号
        void getUrl();                           // 获取用于请求瓦片地图的信息
        void clear();                            // 清空内容
        void quit();                             // 退出下载
        void updateTitle(int x, int y, int z);   // 传出下载的瓦片编号
    
    private:
        QThread* m_thread = nullptr;
        QFuture<void> m_future;
        QRect m_rect;      // 显示瓦片地图像素范围
        int m_level = 5;   // 瓦片地图层级
        QString m_url;
        QSet<quint64> m_exist;        // 已经存在的瓦片地图编号
        QVector<ImageInfo> m_infos;   // 需要下载的瓦片地图信息
    };
    
    #endif   // GETURL_H
  • geturl.cpp

    cpp 复制代码
    /********************************************************************
     * 文件名: geturl.cpp
     * 时间:   2024-05-19 14:29:30
     * 开发者:  mhf
     * 邮箱:   1603291350@qq.com
     * 说明:   瓦片地图网络请求类
     * ******************************************************************/
    #include "geturl.h"
    #include "bingformula.h"
    #include <qnetworkaccessmanager.h>
    #include <qnetworkreply.h>
    #include <QDebug>
    #include <QSet>
    #include <QtConcurrent>
    
    GetUrl::GetUrl(QObject* parent)
        : QObject{parent}
    {
        m_thread = new QThread;
        this->moveToThread(m_thread);
        m_thread->start();
    
        m_rect.setTopLeft(Bing::latLongToPixelXY(64.16, 56.115, m_level));
        m_rect.setBottomRight(Bing::latLongToPixelXY(148.66, 9.34, m_level));
        connect(GetUrlInterface::getInterface(), &GetUrlInterface::updateTitle, this, &GetUrl::updateTitle);
        connect(GetUrlInterface::getInterface(), &GetUrlInterface::showRect, this, &GetUrl::showRect);
        connect(GetUrlInterface::getInterface(), &GetUrlInterface::setLevel, this, &GetUrl::setLevel);
    }
    
    GetUrl::~GetUrl()
    {
        quit();
        clear();
    
        m_thread->quit();
        m_thread->wait();
        delete m_thread;
    }
    
    /**
     * @brief     设置瓦片地图源地址
     * @param url
     */
    void GetUrl::setUrl(QString url)
    {
        if (url.isEmpty())
            return;
    
        quit();   // 退出下载后再清空数组,防止数据竞争
        clear();
        m_exist.clear();   // 清空已下载列表
        m_url = url;
        getImg(m_rect, m_level);   // 使用默认范围、层级更新地图
    }
    
    /**
     * @brief       下载瓦片
     * @param info
     * @return
     */
    void httpGet(ImageInfo info)
    {
        QNetworkAccessManager manager;
        QSharedPointer<QNetworkReply> reply(manager.get(QNetworkRequest(QUrl(info.url))));
        // 等待返回
        QEventLoop loop;
        QObject::connect(reply.data(), &QNetworkReply::finished, &loop, &QEventLoop::quit);   // 等待获取完成
        QTimer::singleShot(5000, &loop, &QEventLoop::quit);                                   // 等待超时
        loop.exec();
    
        if (reply->error() == QNetworkReply::NoError)
        {
            QByteArray buf = reply->readAll();
            if (!buf.isEmpty())
            {
                info.img.loadFromData(buf);
                if (!info.img.isNull())
                {
                    emit GetUrlInterface::getInterface() -> update(info);
                    emit GetUrlInterface::getInterface() -> updateTitle(info.x, info.y, info.z);
                    return;
                }
            }
        }
    
        info.count++;
        if (info.count < 3)
        {
            httpGet(info);   // 下载失败重新下载
            return;
        }
        else
        {
            qWarning() << "下载失败:" << reply->errorString();
        }
    }
    
    /**
     * @brief       获取瓦片地图
     * @param rect  瓦片地图的像素范围
     * @param level 瓦片地图的级别
     */
    void GetUrl::getImg(QRect rect, int level)
    {
        if (rect.isEmpty())
            return;
        if (level > 22 || level < 0)
            return;
        m_rect = rect;
        m_level = level;
    
        if (m_future.isRunning())   // 判断是否在运行
        {
            m_future.cancel();   // 取消下载
        }
        clear();   // 清空待下载列表
    
        getTitle(rect, level);   // 获取所有需要加载的瓦片编号
        qInfo() << "获取瓦片数:" << m_infos.count();
        getUrl();                                         // 将瓦片编号转为url
        m_future = QtConcurrent::map(m_infos, httpGet);   // 在线程池中下载瓦片图
    }
    
    /**
     * @brief      设置获取瓦片地图的像素范围
     * @param rect
     */
    void GetUrl::showRect(QRect rect)
    {
        if (rect.isEmpty())
            return;
        getImg(rect, m_level);
    }
    
    /**
     * @brief       通过设置显示瓦片层级别完成缩放显示
     * @param level
     */
    void GetUrl::setLevel(int level)
    {
        if ((level < 0) || (level > 23))
        {
            return;
        }
        if (m_level != level)
        {
            m_exist.clear();   // 清空已下载列表
        }
        m_level = level;
    }
    
    /**
     * @brief       获取瓦片编号
     * @param rect
     * @param level
     */
    void GetUrl::getTitle(QRect rect, int level)
    {
        QPoint tl = Bing::pixelXYToTileXY(rect.topLeft());
        QPoint br = Bing::pixelXYToTileXY(rect.bottomRight());
    
        quint64 value = 0;
        ImageInfo info;
        info.z = level;
    
        int max = qPow(2, level);   // 最大瓦片编号
        for (int x = tl.x(); x <= br.x(); x++)
        {
            if (x < 0)
                continue;
            if (x >= max)
                break;
            info.x = x;
            for (int y = tl.y(); y <= br.y(); y++)
            {
                if (y < 0)
                    continue;
                if (y >= max)
                    break;
                value = ((quint64) level << 48) + (x << 24) + y;
    
                if (!m_exist.contains(value))
                {
                    info.y = y;
                    m_infos.append(info);
                }
            }
        }
    }
    
    /**
     * @brief 获取用于请求瓦片地图的信息
     */
    void GetUrl::getUrl()
    {
        if (m_url.contains("{x}"))   // XYZ格式
        {
            QString url = m_url;
            url.replace("{x}", "%1");
            url.replace("{y}", "%2");
            url.replace("{z}", "%3");
            for (int i = 0; i < m_infos.count(); i++)
            {
                m_infos[i].url = url.arg(m_infos[i].x).arg(m_infos[i].y).arg(m_infos[i].z);
            }
        }
        else if (m_url.contains("{q}"))   // Bing的quadKey格式
        {
            QString url = m_url;
            url.replace("{q}", "%1");
            QPoint point;
            for (int i = 0; i < m_infos.count(); i++)
            {
                point.setX(m_infos[i].x);
                point.setY(m_infos[i].y);
                QString quadKey = Bing::tileXYToQuadKey(point, m_infos[i].z);   // 将xy转为quadkey
                m_infos[i].url = url.arg(quadKey);
            }
        }
        else
        {
            qDebug() << "url格式未定义";
        }
    }
    
    /**
     * @brief 清空内容
     */
    void GetUrl::clear()
    {
        QVector<ImageInfo> info;
        m_infos.swap(info);
    }
    
    /**
     * @brief 退出下载
     */
    void GetUrl::quit()
    {
        if (m_future.isRunning())   // 判断是否在运行
        {
            m_future.cancel();            // 取消下载
            m_future.waitForFinished();   // 等待退出
        }
    }
    
    /**
     * @brief     将下载成功的瓦片编号添加进已下载列表,已经下载的瓦片在后续不进行下载
     * @param x
     * @param y
     * @param z
     */
    void GetUrl::updateTitle(int x, int y, int z)
    {
        quint64 value = (quint64(z) << 48) + (x << 24) + y;
        m_exist.insert(value);
    }
  • mapgraphicsview.h文件

    cpp 复制代码
    #ifndef MAPGRAPHICSVIEW_H
    #define MAPGRAPHICSVIEW_H
    
    #include "graphicsitemgroup.h"
    #include "mapStruct.h"
    #include <QGraphicsView>
    
    class MapGraphicsView : public QGraphicsView
    {
        Q_OBJECT
    public:
        explicit MapGraphicsView(QWidget* parent = nullptr);
        ~MapGraphicsView() override;
    
        void setRect(int level);
        void drawImg(const ImageInfo& info);
        void clear();
    
    signals:
        void updateImage(const ImageInfo& info);   // 添加瓦片图
        void showRect(QRect rect);
        void mousePos(QPoint pos);
    
    protected:
        void mousePressEvent(QMouseEvent* event) override;
        void mouseReleaseEvent(QMouseEvent* event) override;
        void wheelEvent(QWheelEvent* event) override;
        void resizeEvent(QResizeEvent* event) override;
        void showEvent(QShowEvent* event) override;
    
    private:
        void getShowRect();   // 获取显示范围
    
    private:
        QGraphicsScene* m_scene = nullptr;
        int m_level = 5;           // 当前显示瓦片等级
        bool m_moveView = false;   // 鼠标移动显示视图
        QPointF m_pos;
        QPointF m_scenePos;
        QHash<quint16, GraphicsItemGroup*> m_itemGroup;   // 瓦片图元组
    };
    
    #endif   // MAPGRAPHICSVIEW_H
  • mapgraphicsview.cpp文件

    cpp 复制代码
    #include "mapgraphicsview.h"
    
    #include "bingformula.h"
    #include "geturl.h"
    #include <qthread.h>
    #include <QDebug>
    #include <QFont>
    #include <QGraphicsItem>
    #include <QMouseEvent>
    #include <QScrollBar>
    #include <QWheelEvent>
    #include <QtMath>
    
    MapGraphicsView::MapGraphicsView(QWidget* parent)
        : QGraphicsView(parent)
    {
        m_scene = new QGraphicsScene();
        this->setScene(m_scene);
        this->setDragMode(QGraphicsView::ScrollHandDrag);   // 鼠标拖拽
    
        // 窗口左上角初始显示位置(中国)
        m_scenePos.setX(5700);
        m_scenePos.setY(2700);
        //    this->setMouseTracking(true);                       // 开启鼠标追踪
    
        connect(GetUrlInterface::getInterface(), &GetUrlInterface::update, this, &MapGraphicsView::drawImg);
    }
    
    MapGraphicsView::~MapGraphicsView() {}
    
    void MapGraphicsView::setRect(int level)
    {
        int w = int(qPow(2, level) * 256);
        QRect rect(0, 0, w, w);
        m_scene->setSceneRect(rect);
    
        // 将显示位置移动到缩放之前的位置
        this->horizontalScrollBar()->setValue(qRound(m_scenePos.x() - m_pos.x()));
        this->verticalScrollBar()->setValue(qRound(m_scenePos.y() - m_pos.y()));
    }
    
    /**
     * @brief       绘制瓦片图
     * @param info
     */
    void MapGraphicsView::drawImg(const ImageInfo& info)
    {
        if (!m_itemGroup.contains(info.z))   // 如果图层不存在则添加
        {
            auto* item = new GraphicsItemGroup();
            m_itemGroup.insert(info.z, item);
            m_scene->addItem(item);
        }
    
        GraphicsItemGroup* itemGroup = m_itemGroup.value(info.z);
        if (itemGroup)
        {
            itemGroup->addImage(info);
        }
    }
    
    /**
     * @brief 清空所有瓦片
     */
    void MapGraphicsView::clear()
    {
        auto* itemGroup = m_itemGroup.value(m_level);
        if (itemGroup)
        {
            delete itemGroup;
            m_itemGroup.remove(m_level);
            m_level = 0;
        }
    }
    
    void MapGraphicsView::mousePressEvent(QMouseEvent* event)
    {
        QGraphicsView::mousePressEvent(event);
    
        if (event->buttons() & Qt::LeftButton)
        {
            m_moveView = true;
        }
    }
    
    /**
     * @brief          鼠标释放
     * @param event
     */
    void MapGraphicsView::mouseReleaseEvent(QMouseEvent* event)
    {
        QGraphicsView::mouseReleaseEvent(event);
    
        if (m_moveView)   // 在鼠标左键释放时获取新的瓦片地图
        {
            emit mousePos(this->mapToScene(event->pos()).toPoint());
            getShowRect();
            m_moveView = false;
        }
    }
    
    /**
     * @brief        鼠标滚轮缩放
     * @param event
     */
    void MapGraphicsView::wheelEvent(QWheelEvent* event)
    {
        m_pos = event->pos();                          // 鼠标相对于窗口左上角的坐标
        m_scenePos = this->mapToScene(event->pos());   // 鼠标在场景中的坐标
        if (event->angleDelta().y() > 0)
        {
            m_scenePos = m_scenePos * 2;   // 放大
            m_level++;
        }
        else
        {
            m_scenePos = m_scenePos / 2;   // 缩小
            m_level--;
        }
        m_level = qBound(0, m_level, 22);                            // 限制缩放层级
        setRect(m_level);                                            // 设置缩放后的视图大小
        emit GetUrlInterface::getInterface() -> setLevel(m_level);   // 设置缩放级别
        getShowRect();
    
        // 隐藏缩放前所有图层
        for (auto itemG : m_itemGroup)
        {
            itemG->hide();
        }
    
        if (m_itemGroup.contains(m_level))   // 如果图层存在则显示
        {
            GraphicsItemGroup* itemGroup = m_itemGroup.value(m_level);
            itemGroup->show();
        }
        else   // 如果不存在则添加
        {
            auto* item = new GraphicsItemGroup();
            m_itemGroup.insert(m_level, item);
            m_scene->addItem(item);
        }
    }
    
    /**
     * @brief       窗口大小变化后获取显示新的地图
     * @param event
     */
    void MapGraphicsView::resizeEvent(QResizeEvent* event)
    {
        QGraphicsView::resizeEvent(event);
        //    getShowRect();
    }
    
    /**
     * @brief       窗口显示时设置显示瓦片的视图位置
     * @param event
     */
    void MapGraphicsView::showEvent(QShowEvent* event)
    {
        QGraphicsView::showEvent(event);
        setRect(m_level);
    }
    
    /**
     * @brief 获取当前场景的显示范围(场景坐标系)
     */
    void MapGraphicsView::getShowRect()
    {
        QRect rect;
        int w = int(qPow(2, m_level) * 256);   // 最大范围
        QPoint tl = this->mapToScene(0, 0).toPoint();
        QPoint br = this->mapToScene(this->width(), this->height()).toPoint();
        rect.setX(qMax(tl.x(), 0));
        rect.setY(qMax(tl.y(), 0));
        rect.setRight(qMin(br.x(), w));
        rect.setBottom(qMin(br.y(), w));
        emit GetUrlInterface::getInterface() -> showRect(rect);
    }

4、源码地址

相关推荐
‘’林花谢了春红‘’4 小时前
C++ list (链表)容器
c++·链表·list
----云烟----5 小时前
QT中QString类的各种使用
开发语言·qt
机器视觉知识推荐、就业指导6 小时前
C++设计模式:建造者模式(Builder) 房屋建造案例
c++
GIS思维7 小时前
ArcGIS定义投影与投影的区别(数据和底图不套合的原因和解决办法)
arcgis·gis·地理信息·arcgis坐标系·动态投影
Yang.998 小时前
基于Windows系统用C++做一个点名工具
c++·windows·sql·visual studio code·sqlite3
熬夜学编程的小王8 小时前
【初阶数据结构篇】双向链表的实现(赋源码)
数据结构·c++·链表·双向链表
zz40_8 小时前
C++自己写类 和 运算符重载函数
c++
六月的翅膀8 小时前
C++:实例访问静态成员函数和类访问静态成员函数有什么区别
开发语言·c++
liujjjiyun9 小时前
小R的随机播放顺序
数据结构·c++·算法