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; // 存放地圖圖元組的陣列,以瓦片層級為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