C++(Qt)-GIS开发-QGraphicsView显示瓦片地图简单示例2

C++(Qt)-GIS开发-QGraphicsView显示瓦片地图简单示例2

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

1、概述

  1. 支持多线程加载显示本地离线瓦片地图(墨卡托投影);
  2. 瓦片切片规则以左上角为原点(谷歌、高德、ArcGis等),不支持百度瓦片规则;
  3. 支持显示瓦片网格、编号信息。
  4. 支持鼠标滚轮缩放切换地图层级。
  5. 支持鼠标拖拽。
  6. 采用z/x/y层级瓦片存储格式。
  7. 在单文件中实现所有主要功能,简单便于理解。
  8. 以北纬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);
    }
    
    

4、源码地址

  • 17
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mahuifa

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值