C++(Qt)-GIS开发-QGraphicsView显示瓦片地图简单示例2
更多精彩内容 |
---|
👉个人内容分类汇总 👈 |
👉GIS开发 👈 |
1、概述
- 支持多线程加载显示本地离线瓦片地图(墨卡托投影);
- 瓦片切片规则以左上角为原点(谷歌、高德、ArcGis等),不支持百度瓦片规则;
- 支持显示瓦片网格、编号信息。
- 支持鼠标滚轮缩放切换地图层级。
- 支持鼠标拖拽。
- 采用z/x/y层级瓦片存储格式。
- 在单文件中实现所有主要功能,简单便于理解。
- 以北纬85.05,西经-180为坐标原点【绝对像素坐标】。
开发环境说明
- 系统:Windows11、Ubuntu20.04
- Qt版本:Qt 5.14.2
- 编译器:MSVC2017-64、GCC/G++64
2、实现效果
使用瓦片地图工具下载z/x/y存储格式的瓦片地图进行显示。
3、主要代码
-
bingformula.h
#ifndef BINGFORMULA_H #define BINGFORMULA_H #include <QPoint> #include <QtGlobal> namespace Bing { qreal clip(qreal n, qreal min, qreal max); qreal clipLon(qreal lon); // 裁剪经度范围 qreal clipLat(qreal lat); // 裁剪纬度范围 uint mapSize(int level); // 根据地图级别计算世界地图总宽高(以像素为单位) qreal groundResolution(qreal lat, int level); // 计算地面分辨率 qreal mapScale(qreal lat, int level, int screenDpi); // 计算比例尺 QPoint latLongToPixelXY(qreal lon, qreal lat, int level); // 经纬度转像素 XY坐标 void pixelXYToLatLong(QPoint pos, int level, qreal& lon, qreal& lat); // 像素坐标转WGS-84墨卡托坐标 QPoint pixelXYToTileXY(QPoint pos); // 像素坐标转瓦片编号 QPoint tileXYToPixelXY(QPoint tile); // 瓦片编号转像素坐标 QPoint latLongToTileXY(qreal lon, qreal lat, int level); // 经纬度转瓦片编号 QPointF tileXYToLatLong(QPoint tile, int level); // 瓦片编号转经纬度 QString tileXYToQuadKey(QPoint tile, int level); // 瓦片编号转QuadKey void quadKeyToTileXY(QString quadKey, int& tileX, int& tileY, int& level); // QuadKey转瓦片编号、级别 } // namespace Bing #endif // BINGFORMULA_H
-
bingformula.cpp
/******************************************************************** * 文件名: bingformula.cpp * 时间: 2024-04-05 21:36:16 * 开发者: mhf * 邮箱: 1603291350@qq.com * 说明: 适用于Bing瓦片地图的算法 * ******************************************************************/ #include "bingformula.h" #include <qstring.h> #include <QtMath> static const qreal g_EarthRadius = 6'378'137; // 赤道半径 /** * @brief 限定最小值,最大值范围 * @param n 需要限定的值 * @param min * @param max * @return */ qreal Bing::clip(qreal n, qreal min, qreal max) { n = qMax(n, min); n = qMin(n, max); return n; } /** * @brief 限定经度范围值,防止超限,经度范围[-180, 180] * @param lon 输入的经度 * @return 裁剪后的经度 */ qreal Bing::clipLon(qreal lon) { return clip(lon, -180.0, 180); } /** * @brief 限定纬度范围值,防止超限,经度范围[-85.05112878, 85.05112878] * @param lat 输入的纬度 * @return 裁剪后的纬度 */ qreal Bing::clipLat(qreal lat) { return clip(lat, -85.05112878, 85.05112878); } /** * @brief 根据输入的瓦片级别计算全地图总宽高,适用于墨卡托投影 * @param level 1-23(bing地图没有0级别,最低级别为1,由4块瓦片组成) * @return 以像素为单位的地图宽度和高度。 */ uint Bing::mapSize(int level) { uint w = 256; // 第0级别为256*256 return (w << level); } /** * @brief 计算指定纬度、级别的地面分辨率(不同纬度分辨率不同) * @param lat 纬度 * @param level 地图级别 1-23(bing地图没有0级别,最低级别为1,由4块瓦片组成) * @return 地面分辨率 单位(米/像素) */ qreal Bing::groundResolution(qreal lat, int level) { lat = clipLat(lat); return qCos(lat * M_PI / 180) * 2 * M_PI * g_EarthRadius / mapSize(level); } /** * @brief 计算地图比例尺,地面分辨率和地图比例尺也随纬度而变化 * @param lat 纬度 * @param level 地图级别 1-23(bing地图没有0级别,最低级别为1,由4块瓦片组成) * @param screenDpi 屏幕分辨率,单位为点/英寸 通常为 96 dpi * @return 地图比例尺 1:N(地图上1厘米表示实际N厘米) */ qreal Bing::mapScale(qreal lat, int level, int screenDpi) { return groundResolution(lat, level) * screenDpi / 0.0254; // 1英寸等于0.0254米 } /** * @brief 将一个点从纬度/经度WGS-84墨卡托坐标(以度为单位)转换为指定细节级别的像素XY坐标。 * @param lon 经度 * @param lat 纬度 * @param level 地图级别 * @return 像素坐标 */ QPoint Bing::latLongToPixelXY(qreal lon, qreal lat, int level) { lon = clipLon(lon); lat = clipLat(lat); qreal x = (lon + 180) / 360; qreal sinLat = qSin(lat * M_PI / 180); qreal y = 0.5 - qLn((1 + sinLat) / (1 - sinLat)) / (4 * M_PI); uint size = mapSize(level); qreal pixelX = x * size + 0.5; pixelX = clip(pixelX, 0, size - 1); qreal pixelY = y * size + 0.5; pixelY = clip(pixelY, 0, size - 1); return QPoint(pixelX, pixelY); } /** * @brief 将像素从指定细节级别的像素XY坐标转换为经纬度WGS-84坐标(以度为单位) * @param pos 像素坐标 * @param level * @param lon * @param lat */ void Bing::pixelXYToLatLong(QPoint pos, int level, qreal& lon, qreal& lat) { uint size = mapSize(level); qreal x = (clip(pos.x(), 0, size - 1) / size) - 0.5; qreal y = 0.5 - (clip(pos.y(), 0, size - 1) / size); lon = x * 360; lat = 90 - (360 * qAtan(qExp(-y * 2 * M_PI)) / M_PI); } /** * @brief 像素坐标转瓦片编号 * @param pos 像素坐标 * @return 瓦片编号 */ QPoint Bing::pixelXYToTileXY(QPoint pos) { int x = pos.x() / 256; int y = pos.y() / 256; return QPoint(x, y); } /** * @brief 瓦片编号转像素坐标 * @param tile 瓦片编号 * @return 像素坐标 */ QPoint Bing::tileXYToPixelXY(QPoint tile) { int x = tile.x() * 256; int y = tile.y() * 256; return QPoint(x, y); } /** * @brief 经纬度转瓦片编号 * @param lon * @param lat * @param level * @return */ QPoint Bing::latLongToTileXY(qreal lon, qreal lat, int level) { return pixelXYToTileXY(latLongToPixelXY(lon, lat, level)); } /** * @brief 瓦片编号转经纬度 * @param tile * @param level * @return 经纬度 x:经度 y纬度 */ QPointF Bing::tileXYToLatLong(QPoint tile, int level) { qreal lon = 0; qreal lat = 0; QPoint pos = tileXYToPixelXY(tile); pixelXYToLatLong(pos, level, lon, lat); return QPointF(lon, lat); } /** * @brief 瓦片编号转 bing请求的QuadKey * @param tile 瓦片编号 * @param level 瓦片级别 * @return */ QString Bing::tileXYToQuadKey(QPoint tile, int level) { QString key; for (int i = level; i > 0; i--) { char digit = '0'; int mask = 1 << (i - 1); if ((tile.x() & mask) != 0) { digit++; } if ((tile.y() & mask) != 0) { digit += 2; } key.append(digit); } return key; } /** * @brief 将一个QuadKey转换为瓦片XY坐标。 * @param quadKey * @param tileX 返回瓦片X编号 * @param tileY 返回瓦片Y编号 * @param level 返回瓦片等级 */ void Bing::quadKeyToTileXY(QString quadKey, int& tileX, int& tileY, int& level) { tileX = 0; tileY = 0; level = quadKey.count(); QByteArray buf = quadKey.toUtf8(); for (int i = level; i > 0; i--) { int mask = 1 << (i - 1); switch (buf.at(i - 1)) { case '0': break; case '1': tileX |= mask; break; case '2': tileY |= mask; break; case '3': tileX |= mask; tileY |= mask; break; default: break; } } }
-
mapgraphicsview.h文件
#ifndef MAPGRAPHICSVIEW_H #define MAPGRAPHICSVIEW_H #include "mapStruct.h" #include <QGraphicsView> class MapGraphicsView : public QGraphicsView { Q_OBJECT public: explicit MapGraphicsView(QWidget* parent = nullptr); ~MapGraphicsView() override; void setRect(QRect rect); void drawImg(const ImageInfo& info); void clear(); signals: void updateImage(const ImageInfo& info); // 添加瓦片图 void zoom(bool flag); // 缩放 true:放大 void showRect(QRect rect); void mousePos(QPoint pos); protected: void mouseMoveEvent(QMouseEvent* event) override; void wheelEvent(QWheelEvent* event) override; private: void getShowRect(); // 获取显示范围 private: QGraphicsScene* m_scene = nullptr; QPointF m_pos; QPointF m_scenePos; }; #endif // MAPGRAPHICSVIEW_H
-
mapgraphicsview.cpp文件
#include "mapgraphicsview.h" #include "bingformula.h" #include <QDebug> #include <QGraphicsItem> #include <QMouseEvent> #include <QScrollBar> #include <QWheelEvent> MapGraphicsView::MapGraphicsView(QWidget* parent) : QGraphicsView(parent) { m_scene = new QGraphicsScene(); this->setScene(m_scene); this->setDragMode(QGraphicsView::ScrollHandDrag); // 鼠标拖拽 this->setMouseTracking(true); // 开启鼠标追踪 connect(this, &MapGraphicsView::updateImage, this, &MapGraphicsView::drawImg); } MapGraphicsView::~MapGraphicsView() {} /** * @brief 缩放后设置场景大小范围 * @param rect */ void MapGraphicsView::setRect(QRect rect) { m_scene->setSceneRect(rect); // 将显示位置移动到缩放之前的位置 this->horizontalScrollBar()->setValue(qRound(m_scenePos.x() - m_pos.x())); this->verticalScrollBar()->setValue(qRound(m_scenePos.y() - m_pos.y())); getShowRect(); } /** * @brief 绘制瓦片图 * @param info */ void MapGraphicsView::drawImg(const ImageInfo& info) { // 绘制瓦片图 auto item = m_scene->addPixmap(info.img); QPoint pos = Bing::tileXYToPixelXY(QPoint(info.x, info.y)); item->setPos(pos); // 绘制边框 auto itemR = m_scene->addRect(0, 0, 255, 255, QPen(Qt::red)); itemR->setPos(pos); } /** * @brief 清空所有瓦片 */ void MapGraphicsView::clear() { m_scene->clear(); } /** * @brief 获取鼠标移动坐标 * @param event */ void MapGraphicsView::mouseMoveEvent(QMouseEvent* event) { QGraphicsView::mouseMoveEvent(event); emit mousePos(this->mapToScene(event->pos()).toPoint()); getShowRect(); } /** * @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; // 放大 emit this->zoom(true); } else { m_scenePos = m_scenePos / 2; // 缩小 emit this->zoom(false); } } /** * @brief 获取当前场景的显示范围(场景坐标系) */ void MapGraphicsView::getShowRect() { QRect rect; rect.setTopLeft(this->mapToScene(0, 0).toPoint()); rect.setBottomRight(this->mapToScene(this->width(), this->height()).toPoint()); emit this->showRect(rect); }