QGIS开发笔记(四):QgsRasterLayer加载Cesium二维地图的瓦片地图数据到QGIS

前言

地图引擎加载瓦片地图是基本操作,本篇对qgis添加图片瓦片地图。

Demo

专业名词

波段

波段(Band) 是指栅格数据中具有特定波长范围的信息层,类似于数字图像中的 "通道"。它是栅格数据(尤其是遥感影像)的核心组成部分,不同波段对应地物在不同电磁波谱范围内的反射或辐射特性。

不同波段的电磁波与地物相互作用的方式不同,因此能反映地物的不同特征:

  • 单波段灰度:选择单个波段,以黑白灰度显示(如单独查看近红外波段);
  • 多波段色彩:将3个不同波段分别映射到红(R)、绿(G)、蓝(B)通道,形成彩色图像(如真彩色、假彩色合成);
  • 伪彩色:对单波段数据使用色带渐变显示(如 DEM 的高程渲染)

图层

图层是管理和展示地理数据的核心单元,所有空间数据(如矢量数据、栅格数据、瓦片地图等)都以图层形式加载和处理。QGIS 支持多种类型的图层:

  • 矢量图层(Vector Layers)
  • 栅格图层(Raster Layers)
  • 瓦片图层(Tile Layers)
  • 标注图层(Label Layers)
  • 网格图层(Grid Layers)
  • 三维图层(3D Layers)
  • 其他图层

栅格数据

一种以规则网格(像素或像元)形式存储的地理空间数据,每个网格单元(像素)包含一个或多个数值,用于表示连续的地理现象(如地形、温度、植被覆盖等)。QGIS 支持多种栅格格式,常见的包括:

  • TIFF/GeoTIFF(.tif/.tiff):最常用的带地理信息的栅格格式,支持多波段、压缩和空间参考。
  • JPEG/JPEG2000(.jpg/.jp2):常用于卫星影像或航空照片,压缩率高。
  • PNG(.png):支持透明通道,适合作为底图或符号。
  • GRID(ArcGIS Grid):ESRI 的栅格格式,由多个文件组成。
  • NetCDF(.nc):用于存储气象、海洋等多维栅格数据。
  • HDF(.hdf):常用于遥感数据(如 MODIS、Landsat)。

QgsRasterLayer

概述

QgsRasterLayer是处理栅格数据的核心类,负责加载、显示渲染和操作栅格图层(如卫星影像、DEM 数据、遥感图像等)。

QgsRasterLayer对象通常由QgsProject管理生命周期,添加到项目后不需要手动删除。

QGIS的大部分API不是线程安全的,应避免在非主线程中操作栅格图层。

QgsRasterLayer支持多种栅格格式,具体取决于编译 QGIS 时启用的 GDAL 驱动。

核心功能与特性

  • 栅格数据加载:支持多种栅格格式(GeoTIFF、JPEG、PNG、DEM 等),底层通过GDAL库实现格式解析。
  • 属性信息获取:可获取图层范围、分辨率、波段数、坐标参考系(CRS)等元数据。
  • 像素数据访问:提供接口读取指定坐标或区域的像素值,支持单像素查询和区域数据提取。
  • 渲染控制: 支持多种渲染方式(单波段灰度、多波段 RGB、伪彩色等),可自定义颜色映射。
  • 项目集成:可添加到QgsProject中进行管理,与地图画布(QgsMapCanvas)联动显示。

常用属性和操作

创建栅格图层

cpp 复制代码
// 栅格文件路径和图层名称
QString rasterPath = "/path/to/your/raster.tif";
QString layerName = "My Raster Layer"; // 创建栅格图层 QgsRasterLayer* rasterLayer = new QgsRasterLayer(rasterPath, layerName); // 检查图层是否有效 if (!rasterLayer->isValid()) { qDebug() << "栅格图层加载失败: " << rasterLayer->error().message(); delete rasterLayer; return 1; } 

获取栅格图层属性信息

cpp 复制代码
// 获取图层范围
QgsRectangle extent = rasterLayer->extent(); qDebug() << "图层范围: " << "最小X:" << extent.xMinimum() << ", 最小Y:" << extent.yMinimum() << "最大X:" << extent.xMaximum() << ", 最大Y:" << extent.yMaximum(); // 获取波段数量 int bandCount = rasterLayer->bandCount(); qDebug() << "波段数量: " << bandCount; // 获取坐标参考系 QgsCoordinateReferenceSystem crs = rasterLayer->crs(); qDebug() << "坐标参考系: " << crs.authid() << " - " << crs.description(); // 获取分辨率 double xRes = rasterLayer->rasterUnitsPerPixelX(); double yRes = rasterLayer->rasterUnitsPerPixelY(); qDebug() << "分辨率: X=" << xRes << ", Y=" << yRes; 

读取栅格像素值

cpp 复制代码
#include <qgspointxy.h>
#include <qgsrasterdataprovider.h> // 读取指定坐标的像素值 QgsPointXY point(123456.78, 987654.32); // 地图坐标 int bandNumber = 1; // 波段索引(从1开始) bool success = false; double value = rasterLayer->dataProvider()->sample(point, bandNumber, success); if (success) { qDebug() << "坐标 (" << point.x() << ", " << point.y() << ") 的像素值: " << value; } else { qDebug() << "读取像素值失败"; } 

设置栅格图层渲染方式

cpp 复制代码
#include <qgsrasterrenderer.h>
#include <qgsrastershader.h> #include <qgsrasterinterface.h> // 对于单波段栅格设置灰度渲染 if (bandCount == 1) { // 获取当前渲染器 QgsRasterRenderer* renderer = rasterLayer->renderer(); // 设置渲染范围 renderer->setClassificationMin(0); renderer->setClassificationMax(255); // 触发重绘 rasterLayer->triggerRepaint(); } 

QGis加载瓦片地图的配置

加载在线XYZ瓦片的XML配置

在线XYZ瓦片的XML配置文件通常用于批量导入多个瓦片服务连接。示例如下:

xml 复制代码
<qgsXYZTilesConnections version="1.0"> <xyztiles url="https://services.arcgisonline.com/arcgis/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}" name="ESRI 卫星" authcfg="" password="" http-header:referer="https://www.arcgis.com/" referer="https://www.arcgis.com/" tilePixelRatio="1" zmin="1" username="" zmax="20"/> <xyztiles url="https://map.geoq.cn/arcgis/rest/services/ChinaOnlineCommunity/MapServer/tile/{z}/{y}/{x}" name="ESRI 道路" authcfg="" password="" http-header:referer="http://www.arcgisonline.cn/" referer="http://www.arcgisonline.cn/" tilePixelRatio="1" zmin="1" username="" zmax="19"/> </qgsXYZTilesConnections> 

加载离线XYZ瓦片的XML配置

离线瓦片的 XML 配置文件需要遵循 GDAL_WMS 规范,示例如下:

xml 复制代码
<GDAL_WMS>
	
	<!-- ServerUrl:瓦片服务的 URL 模板,${z}表示缩放级别,${x}和${y}表示瓦片坐标 --> <Service name="TMS"> <!-- <ServerUrl>file:///E:/work/5-QGis/2-doc/testDemo/build/Image/${z}/${x}/${y}.png</ServerUrl> --> <!-- <ServerUrl>file:///D:/qtProject/qGisDemo/recv/testDemo/build/Image/${z}/${x}/${y}.png</ServerUrl> --> <ServerUrl>file:///D:/qtProject/qGisDemo/qGisTest/image/${z}/${x}/${y}.png</ServerUrl> <ImageFormat>image/png</ImageFormat> </Service> <!-- DataWindow:定义瓦片服务的地理范围 --> <DataWindow> <!-- 坐标范围,通常使用Web Mercator投影的范围 --> <UpperLeftX>-180</UpperLeftX> <UpperLeftY>85.0521</UpperLeftY> <LowerRightY>-85.0472</LowerRightY> <LowerRightX>179.995</LowerRightX> <!-- TileLevel:最大缩放级别,当前级别6级:0->6文件夹 --> <TileLevel>6</TileLevel> <!-- 表示在 X 轴(横向)方向上的瓦片数量为 2。即当前视图在水平方向上由 2 个瓦片拼接而成。 --> <TileCountX>2</TileCountX> <!-- 表示在 Y 轴(纵向)方向上的瓦片数量为 1。即当前视图在垂直方向上由 1 个瓦片构成。 --> <TileCountY>1</TileCountY> <!-- Y轴原点位置,TMS通常为top,XYZ服务可能为bottom --> <YOrigin>bottom</YOrigin> </DataWindow> <!-- Projection:坐标系,在线瓦片使用 WGS84 --> <!-- <Projection>WGS84</Projection> --> <!-- Projection:坐标系,大多数在线瓦片使用 EPSG:3857,qgis使用这个加载不出来报错,需要带上proj.db数据库文件 --> <Projection>EPSG:3857</Projection> <!-- 瓦片大小,通常为256x256像素 --> <BlockSizeX>256</BlockSizeX> <BlockSizeY>256</BlockSizeY> <!-- 波段数量,RGB为3,包含透明通道为4 --> <BandsCount>3</BandsCount> <Cache> <!-- 本地缓存路径 --> <Path>D:/qtProject/qGisDemo/qGisTest/qgis_tiles_cache</Path> </Cache> </GDAL_WMS> 

关于拽托渲染延迟刷新

概述

拖拽地图时不实时刷新、松开后延迟刷新的现象,与地图渲染策略和事件处理机制有关。

  • 渲染性能优化机制:QGIS为避免拖拽过程中频繁渲染导致的卡顿,**默认采用"拖拽时暂停渲染,松开后再刷新"的策略。**这是对低性能设备的保护机制,尤其当栅格图层分辨率高、数据量大时,实时渲染会消耗大量CPU/GPU资源。
  • 事件处理优先级:拖拽操作(如鼠标拖动QgsMapCanvas)属于高频事件(如mouseMoveEvent)。若在事件触发时立即调用刷新接口(如refresh()或repaint()),会导致事件队列阻塞,反而降低体验。
  • 栅格图层的特殊性:栅格数据渲染需要处理大量像素(尤其是大尺寸/高分辨率图层),拖拽时实时重绘可能导致明显延迟,因此框架默认选择延迟刷新。

实时刷新方案一:调整画布刷新策略

cpp 复制代码
// 获取地图画布指针(假设已初始化)
QgsMapCanvas* canvas = ...; // 禁用"拖拽时暂停渲染"的优化 canvas->setParallelRenderingEnabled(true); // 启用并行渲染(多核加速) canvas->setRenderFlag(true); // 强制始终允许渲染 // 调整刷新触发方式:在鼠标移动时主动触发刷新 connect(canvas, &QgsMapCanvas::extentsChanged, canvas, &QgsMapCanvas::refresh); 

实时刷新方案二:优化栅格图层渲染性能

若实时刷新仍卡顿,可降低渲染负载:

cpp 复制代码
// 临时降低栅格图层分辨率(拖拽时)
QgsRasterLayer* rasterLayer = ...; rasterLayer->setDrawingStyle(QgsRasterLayer::DrawingStyle::Preview); // 预览模式(低分辨率) // 拖拽结束后恢复高质量渲染 // 在鼠标释放事件中: rasterLayer->setDrawingStyle(QgsRasterLayer::DrawingStyle::Default); canvas->refresh(); 

实时刷新方案三:自定义事件处理通过重写QgsMapCanvas的鼠标事件,控制刷新时机

cpp 复制代码
class CustomMapCanvas : public QgsMapCanvas { Q_OBJECT public: using QgsMapCanvas::QgsMapCanvas; protected: void mouseMoveEvent(QMouseEvent* e) override { // 调用父类方法处理拖拽逻辑 QgsMapCanvas::mouseMoveEvent(e); // 拖拽过程中主动刷新(仅在鼠标按下时) if (e->buttons() & Qt::LeftButton) { this->refresh(); // 实时刷新 } } }; 

关于图层QGgsProject

经过简单测试,确认画布添加图层,这个QGgsProject里面不添加图层:

这个全局类应该是为了方便一些缓存调用,省的自己管理类,还有一些函数,比如图层是否显示是否包含等等基本操作,还有一个最重要的是项目结束的时候,添加进去的元素项目类实例都会自动销毁,无需手动销毁。

Demo源码

cpp 复制代码
void QGisWidget::test_demo2(QString filePath) { // 步骤一:创建画布 QgsMapCanvas *pMapCanvas = new QgsMapCanvas(); // 启用并行渲染 pMapCanvas->setParallelRenderingEnabled(true); // 强制始终允许渲染 pMapCanvas->setRenderFlag(true); // 步骤二:创建栅格图层 QgsRasterLayer *pLayer = 0; // new的时候就赋值 pLayer = new QgsRasterLayer(filePath); pMapCanvas->setLayers({pLayer}); pMapCanvas->setExtent(pLayer->extent()); pMapCanvas->refresh(); // 步骤三:需要拽托移动,则需要QgsMapToolPan,否则无法移动只能滚轮缩放 QgsMapToolPan *pMapToolPan = new QgsMapToolPan(pMapCanvas); pMapCanvas->setMapTool(pMapToolPan); // 布局初始化 // 注意:这里使用动态创建控件 QHBoxLayout *pHBoxLayout = dynamic_cast<QHBoxLayout *>(this->layout()); if(!pHBoxLayout) { pHBoxLayout = new QHBoxLayout(this); LOG; } pHBoxLayout->addWidget(pMapCanvas, 1); pHBoxLayout->setMargin(0); setLayout(pHBoxLayout); } 

工程模板v1.1.0

入坑

入坑一:纵横比显示错误

问题

纵横比显示错误,导致变形

原因

Xml的坐标系范围不对

解决

修正xml配置参数

入坑二:修改坐标系种类后空白

问题

修改坐标系后空表

尝试

确保投影定义使用正确的EPSG代码格式,确认没问题。

打开桌面版QGIS看他支持不:

是支持的。

解决

是数据库.db的问题,部署环境的时候,需要带上:

开发环境的把proj.db带上去,再测试可以了: