QgraphicsView异步线程加载地图瓦片

        本节主要记录一下qt开发过程中离线地图瓦片的加载方式,瓦片加载选择graphicsView控件,同时为了不影响主线程事件和其他操作,这里采用了异步线程的操作,将地图瓦片加载的步骤放到了异步子线程之中。注:本记录仅为本人笔记记录。

 一、效果展示

二、代码展示

        1、思路

        设计中,我们先选择graphicsView放置与ui设计界面,以作为瓦片的存放容器。

        创建子线程:线程功能实现为筛选对应坐标编号的地图瓦片信息并传递给主线程进行显示

        主线程:开启子线程的运行,并获取子线程传递的对应瓦片地图信息进行显示。

        2、子线程代码实现

#ifndef MAPWORK_H
#define MAPWORK_H

#include <QObject>
#include <QPixmap>
#include <QFile>
class mapWork : public QObject
{  
    Q_OBJECT
public:
    explicit mapWork(int zoom, int tileSize, QObject *parent = nullptr)
        : QObject(parent), m_zoom(zoom), m_tileSize(tileSize) {}

public slots:
    //自定义函数,是实现瓦片的查找和加载
    void loadTiles(int xStart, int xEnd, int yStart, int yEnd, const QString& basePath) {
        //X层级和y层级代表地图瓦片编号相对应的层级
        for(int x = xStart; x < xEnd; ++x) {
            for(int y = yStart; y < yEnd; ++y) {
                //我的瓦片图文件存放在程序目录下的mapabc目录,satellite目录对应的是卫星地图瓦片,overlay目录对应的是街道瓦片数据
                //先加载卫星瓦片地图
                QString filename_1 = QString("%1/mapabc/satellite/%2/%3/%4.jpg")
                                            .arg(basePath)
                                            .arg(m_zoom)
                                            .arg(x)
                                            .arg(y);
                if(QFile::exists(filename_1)) {
                    QPixmap pixmap(filename_1);
                    if(!pixmap.isNull()) {
                        emit tileLoaded(x, y, pixmap);//发送瓦片数据信息  对应的层级和图片文件
                    }

                //加载街道地图瓦片
                QString filename_2 = QString("%1/mapabc/overlay/%2/%3/%4.png")
                                             .arg(basePath)
                                             .arg(m_zoom)
                                             .arg(x)
                                             .arg(y);
                if(QFile::exists(filename_2)) {
                    QPixmap pixmap2(filename_2);
                    if(!pixmap.isNull()) {
                        emit tileLoaded(x, y, pixmap2);//发送瓦片数据信息  对应的层级和图片文件
                    }
                    }
                }
            }
            emit finished();
        }
    }

signals:
    void tileLoaded(int x, int y, const QPixmap& pixmap);  //定义信号,传递主线程对应的瓦片信息
    void finished();

private:
    int m_zoom;  //瓦片层级
    int m_tileSize; //瓦片的大小 256*256,此处为256
};

#endif // MAPWORK_H

3、主函数代码功能实现

mainwindow.h文件

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QGraphicsScene> //场景
#include <QGraphicsView>  //视图
#include <QGraphicsItem>  //图元
#include <QDir>
#include "mapwork.h"
#include <QThread>
QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    void setMap();
private slots:
    void addTileToScene(int x, int y, const QPixmap& pixmap);

private:
    void setupThread();

    Ui::MainWindow *ui;
    QGraphicsScene *myScene;
    //map 线程加载
    mapWork *m_worker;
    QThread *m_workerThread;

    //map相关
    int zoom = 14;//地图层级
    int wap_X_start = 13373; //x层级瓦片和y层级瓦片的开始结束标号
    int wap_X_end = 13604;
    int wap_Y_start = 6123;
    int wap_Y_end = 6290;

    //程序路径
    QDir currentDir = QDir::current();

    int tileSize = 256;//每个瓦片的像素边长 256*256
};
#endif // MAINWINDOW_H

mainwindow.cpp文件

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QPixmap>
#include <QTimer>
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent), ui(new Ui::MainWindow),
    zoom(14),
    currentDir(QDir::current()),
    tileSize(256)
{
    ui->setupUi(this);
    setupThread();//进入线程加载图片
    //场景设置
    myScene = new QGraphicsScene(this);
    //初始化视图
    ui->graphicsView->setScene(myScene);
    ui->graphicsView->setRenderHint(QPainter::SmoothPixmapTransform);
    ui->graphicsView->setCacheMode(QGraphicsView::CacheBackground);
    setMap();
}
MainWindow::~MainWindow()
{
    delete ui;

    if(m_workerThread && m_workerThread->isRunning()) {
        qDebug() << "Stopping worker thread...";
        m_workerThread->quit();
        if(!m_workerThread->wait(3000)) {
            qCritical() << "线程未正常退出,强制终止";
            m_workerThread->terminate();
        }
    }
}
void MainWindow::setupThread()
{
    m_workerThread = new QThread(this);
    m_worker = new mapWork(zoom, tileSize);
    m_worker->moveToThread(m_workerThread);

    // 完整信号连接
    connect(m_worker, &mapWork::tileLoaded,
            this, &MainWindow::addTileToScene);
    connect(m_workerThread, &QThread::finished,
            m_worker, &QObject::deleteLater);

    m_workerThread->start();
}

void MainWindow::setMap()
{
    const QString basePath = currentDir.absolutePath();
    //线程安全
    QMetaObject::invokeMethod(m_worker, "loadTiles", Qt::QueuedConnection,
                              Q_ARG(int, wap_X_start),
                              Q_ARG(int, wap_X_end),
                              Q_ARG(int, wap_Y_start),
                              Q_ARG(int, wap_Y_end),
                              Q_ARG(QString, basePath));
}
//插入图片元素
void MainWindow::addTileToScene(int x, int y, const QPixmap& pixmap)
{
    QGraphicsPixmapItem* item = myScene->addPixmap(pixmap);
    item->setPos(x * tileSize, y * tileSize);
}

 

### 创建瓦片游戏地图C++实现 #### 使用`QGraphicsView`框架展示瓦片地图 为了创建基于C++的游戏地图,可以借鉴GIS开发中的经验,在Qt环境中利用`QGraphicsView`来显示瓦片地图。此方法不仅适用于地理信息系统(GIS),同样适合于制作具有固定视角的角色扮演游戏(RPG)或其他类型的二维游戏世界[^1]。 ```cpp // 初始化场景并设置视图范围 QGraphicsScene* scene = new QGraphicsScene(); ui->graphicsView->setScene(scene); scene->setSceneRect(-mapWidth / 2, -mapHeight / 2, mapWidth, mapHeight); // 加载指定层级的地图切片图像至场景中 void loadTiles(int zoomLevel){ for (int x = minX; x <= maxX; ++x){ for (int y = minY; y <= maxY; ++y){ QString imagePath = QString("%1/%2/%3.png").arg(zoomLevel).arg(x).arg(y); QPixmap tilePixmap(imagePath); QGraphicsPixmapItem *item = new QGraphicsPixmapItem(tilePixmap); item->setPos(QPointF(x*tileSize-width/2,y*tileSize-height/2)); scene->addItem(item); } } } ``` 上述代码片段展示了如何初始化图形场景以及加载特定缩放级别的瓦片图片到场景里。这里假设已经存在按照标准XYZ模式组织好的瓦片资源文件夹结构[^4]。 #### 计算与转换坐标系 当涉及到实际游戏中角色移动或者交互逻辑时,可能需要频繁地在屏幕空间和瓦片坐标之间互相转化。这可以通过下面的方式完成: - **从经纬度转成瓦片坐标**:对于给定的一组经纬度值,可以根据公式将其映射为对应的瓦片位置。这一过程类似于处理地球表面数据时的操作[^2]。 ```cpp double lat_rad = deg_to_rad(latitude); // 将纬度由角度制转化为弧度制 double n = pow(2.0, zoom_level); double tile_x = ((longitude + 180.0) / 360.0 * n); double tile_y = (1 -(log(tan(lat_rad) + 1/cos(lat_rad)) / M_PI) / 2.0 * n); ``` - **从瓦片坐标转回像素偏移量**:一旦知道了某一点所在的瓦片索引,就可以很容易地得到它相对于整个地图窗口的位置了。 ```cpp float pixel_offset_x = (tile_x - origin_tile.x()) * TILE_SIZE; float pixel_offset_y = (tile_y - origin_tile.y()) * TILE_SIZE; ``` 以上操作能够有效地支持玩家在游戏中自由探索不同区域的同时保持良好的性能表现。 #### 多线程优化与异步加载机制 考虑到大型开放世界的复杂性和实时渲染的需求,引入多线程技术显得尤为重要。通过合理分配任务给多个CPU核心执行,可以在不影响用户体验的前提下加快地图元素的呈现速度。特别是针对那些远离当前视野中心但又即将进入可视范围内的部分提前做好准备。 ```cpp class TileLoader : public QObject { public slots: void fetchTile(const QPoint& pos){ QNetworkAccessManager manager(this); connect(&manager,SIGNAL(finished(QNetworkReply*)),this,SLOT(handleDownloadedData(QNetworkReply*))); QUrl url(QString("http://example.com/tiles/%1/%2/%3.png").arg(pos.x()).arg(pos.y())); manager.get(QNetworkRequest(url)); } private slots: void handleDownloadedData(QNetworkReply *reply){ QByteArray data = reply->readAll(); QImage image; if (!image.loadFromData(data)){ qDebug()<<"Failed to decode image"; return ; } emit tileReady(pos,image); } signals: void tileReady(const QPoint&, const QImage&); }; ``` 这段示例演示了一个简单的网络请求处理器用于获取远程服务器上的瓦片图像,并通知主线程更新界面。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值