mbtiles瓦片地图拼接显示qt Cpp源码,瓦片地图拼接,瓦片地图显示,可导入*.mbtiles文件,支持多层级拼接与缺块拼接,支持鼠标缩放,平移。

在GIS开发中,瓦片地图的拼接与显示是一个常见需求。最近我用Qt C++实现了一个支持MBTiles格式的瓦片地图查看器,感觉挺有意思,分享一下实现思路和代码片段。

首先,MBTiles是一种轻量级的地图瓦片存储格式,它基于SQLite数据库,把所有的瓦片数据存储在一个文件中。我们可以用Qt的SQL模块来读取它:

QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName("map.mbtiles");
if (!db.open()) {
    qDebug() << "Failed to open mbtiles file";
    return;
}

QSqlQuery query;
query.exec("SELECT tile_data FROM tiles WHERE zoom_level = 0 AND tile_column = 0 AND tile_row = 0");
if (query.next()) {
    QByteArray tileData = query.value(0).toByteArray();
    // 处理瓦片数据
}

读取到瓦片数据后,我们需要在Qt中显示它们。这里我用QGraphicsView和QGraphicsScene来构建地图显示框架:

class MapView : public QGraphicsView {
    Q_OBJECT
public:
    MapView(QWidget* parent = nullptr) : QGraphicsView(parent) {
        setScene(new QGraphicsScene(this));
        setRenderHint(QPainter::Antialiasing);
        setDragMode(QGraphicsView::ScrollHandDrag);
        setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
    }

protected:
    void wheelEvent(QWheelEvent* event) override {
        // 实现鼠标滚轮缩放
        qreal factor = qPow(1.2, event->angleDelta().y() / 240.0);
        scale(factor, factor);
    }
};

接下来是重头戏 - 瓦片拼接。我们需要根据当前视图范围和缩放级别,计算出需要加载的瓦片坐标,然后从MBTiles中读取相应的瓦片数据并显示:

void MapScene::updateTiles() {
    // 获取当前视图范围
    QRectF viewRect = mapToScene(viewport()->rect()).boundingRect();
    
    // 计算当前缩放级别
    int zoom = qRound(log2(transform().m11()));
    
    // 计算需要加载的瓦片范围
    int tileSize = 256;  // 标准瓦片大小
    int minX = qFloor(viewRect.left() / tileSize);
    int maxX = qCeil(viewRect.right() / tileSize);
    int minY = qFloor(viewRect.top() / tileSize);
    int maxY = qCeil(viewRect.bottom() / tileSize);
    
    // 加载并显示瓦片
    for (int x = minX; x <= maxX; ++x) {
        for (int y = minY; y <= maxY; ++y) {
            // 从MBTiles读取瓦片数据
            QImage tile = loadTileFromMBTiles(zoom, x, y);
            if (!tile.isNull()) {
                // 在场景中添加瓦片
                QGraphicsPixmapItem* item = new QGraphicsPixmapItem(QPixmap::fromImage(tile));
                item->setPos(x * tileSize, y * tileSize);
                addItem(item);
            }
        }
    }
}

这里有个小技巧,为了处理瓦片缺失的情况,我们可以实现一个瓦片金字塔机制。当某个层级的瓦片不存在时,自动尝试加载上一级更粗糙的瓦片,直到找到可用的瓦片为止:

QImage MapScene::loadTileFromMBTiles(int zoom, int x, int y) {
    QImage tile;
    int currentZoom = zoom;
    
    while (currentZoom >= 0) {
        // 读取瓦片
        tile = readTileFromDB(currentZoom, x, y);
        if (!tile.isNull()) {
            // 如果找到了瓦片,按当前缩放级别调整大小
            if (currentZoom != zoom) {
                tile = tile.scaled(tile.width() * (1 << (zoom - currentZoom)),
                                 tile.height() * (1 << (zoom - currentZoom)));
            }
            return tile;
        }
        
        // 没找到,尝试上一级瓦片
        currentZoom--;
        x /= 2;
        y /= 2;
    }
    
    return QImage();  // 所有层级都找不到,返回空图像
}

最后,为了让地图操作更流畅,我实现了一个简单的瓦片缓存机制。把最近加载的瓦片存储在内存中,避免频繁访问数据库:

class TileCache {
public:
    QImage getTile(int zoom, int x, int y) {
        QString key = QString("%1-%2-%3").arg(zoom).arg(x).arg(y);
        if (cache.contains(key)) {
            return cache[key];
        }
        QImage tile = loadTileFromMBTiles(zoom, x, y);
        if (!tile.isNull()) {
            cache.insert(key, tile);
            if (cache.size() > maxSize) {
                cache.remove(cache.keys().first());
            }
        }
        return tile;
    }

private:
    QMap<QString, QImage> cache;
    int maxSize = 100;  // 最大缓存瓦片数
};

这样,一个基本的MBTiles瓦片地图查看器就完成了。当然,这只是一个简单的实现,还有很多可以优化的地方,比如:

  1. 添加异步加载机制,避免界面卡顿
  2. 实现更智能的预加载,提前加载即将进入视图的瓦片
  3. 支持更多地图操作,如旋转、测量等
  4. 添加图层管理,支持叠加多个MBTiles文件

总的来说,用Qt实现MBTiles地图查看器是一个很好的练习项目,既能学习Qt的图形编程,又能了解GIS开发的基本原理。如果你对地图开发感兴趣,不妨自己动手实现一个,相信会有不少收获。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐