C++(Qt)-GIS开发-QGraphicsView显示在线瓦片地图 原创
C++(Qt)-GIS开发-QGraphicsView显示在线瓦片地图
更多精彩内容 |
---|
👉个人内容分类汇总 👈 |
👉GIS开发 👈 |
1、概述
- 支持加载显示在线瓦片地图(墨卡托投影);
- 瓦片切片规则以左上角为原点(谷歌、高德、ArcGis等),不支持百度瓦片规则;
- 支持显示瓦片网格、编号信息。
- 支持鼠标滚轮缩放切换地图层级。
- 支持鼠标拖拽。
- 支持显示瓦片编号、瓦片网格;
- 支持在线程池中快速下载在线瓦片;
- 以北纬85.05,西经-180为坐标原点【绝对像素坐标】。
- 默认支持下载显示多格式高德、Bing、ArcGis瓦片地图。
- 支持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
#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
/******************************************************************** * 文件名: 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文件
#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文件
#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); }