C++(Qt)-GIS開發-QGraphicsView顯示瓦片地圖簡單示例

mahuifa發表於2024-07-06

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

相關文章