资讯详情

资讯详情

Cesium瓦片底图迁移QGIS:QgsRasterLayer加载XYZ瓦片实战指南

做QGIS二次开发的人估计都遇到过这个需求想把Cesium里看到的那套二维瓦片底图搬到QGIS桌面端来作为底图叠加分析。标题里提到的QgsRasterLayer看起来是一个很基础的类但真正用它来加载Cesium常用的那种XYZ瓦片数据时里面还是有不少门道。这篇笔记我结合实际项目经验把从原理到实操的完整过程梳理一遍该绕的坑我都标出来免得你再走弯路。先说结论Cesium里最常见的二维地图底图本质上就是一套Web Mercator投影的XYZ瓦片切片而QGIS桌面端想要加载这类数据最直接的方式就是用QgsRasterLayer配合xyz数据提供器。这个方案不用装插件、不用写复杂的数据转换逻辑几行代码就能把高德、天地图、OSM这类在线瓦片叠到工程里和矢量要素、分析结果放在一起看。这篇笔记适合下面几类人刚接触QGIS二次开发、想弄明白QgsRasterLayer怎么用的人正在做Cesium与桌面GIS联动、想把Web端底图迁移到QGIS工作流里的开发者以及被瓦片加载黑屏、坐标偏移、图源失效折腾过的朋友。1. 为什么要把Cesium的二维瓦片地图搬进QGIS先聊一下这个需求是怎么来的。Cesium作为三维地球前端库默认加载的底图通常是高德、天地图这类在线XYZ瓦片或者Cesium官方提供的影像服务。这些底图在浏览器里显示得很好但到了桌面端做数据生产、空间分析的时候Cesium就使不上劲了。这时候大家会想到QGIS。QGIS是桌面GIS环境里的“瑞士军刀”矢量编辑、栅格处理、坐标系转换、打印出图都是强项。实际项目里经常是这种工作流前端用Cesium做展示后端用QGIS做数据准备和成果检查。两边如果不能共用同一套底图就会出大问题。我遇到过最典型的情况甲方给的矢量数据是基于某个在线瓦片底图手工绘制的但同事在QGIS里默认用OSM底图叠加同一份数据结果边界对不上花了一下午排查才发现是底图投影和来源不一致。后来统一用同一套高德瓦片作为底图问题立刻消失。也就是说把Cesium二位的瓦片地图数据加载到QGIS不只是“图好看”的问题还关系到整个数据生产链路的一致性。而QgsRasterLayer就是QGIS里打通这条链路的关键入口。2. QgsRasterLayer加载瓦片的核心机制2.1 QgsRasterLayer到底是什么QgsRasterLayer是QGIS栅格图层的核心类意思是“栅格图层”。在QGIS的C SDK里几乎所有栅格数据都要通过它来创建和管理包括普通影像、DEM、以及这里说的在线瓦片。很多初学者会误以为在线瓦片和普通影像不一样可能需要专门的类。实际上QGIS的架构做得很好它把底层的数据访问逻辑抽象成了“数据提供器”也就是provider。QgsRasterLayer本身不关心数据是从本地文件来还是从HTTP请求来它只负责提供一个统一的图层接口。比如加载本地GeoTIFF可以这么写QgsRasterLayer* layer new QgsRasterLayer(/path/to/dem.tif, DEM, gdal);加载在线瓦片关键区别就在第三个参数上也就是provider的名字QgsRasterLayer* layer new QgsRasterLayer(url, 在线底图, xyz);看到没有接口是一样的只是provider从gdal换成了xyz。这就是QGIS插拔式架构的好处。明白了这层关系后续的很多问题就都好理解了。2.2 xyz provider与URL模板规则xyz provider是QGIS 3.x开始内置的一个瓦片数据提供器。它的工作方式很简单QGIS需要显示某个范围时根据当前比例尺计算出需要哪些瓦片然后把URL模板里的{x}、{y}、{z}替换成实际的瓦片行列号和缩放级别依次请求瓦片图片拼接显示。URL模板是这个方案的灵魂。以高德道路图为例常见的是这种https://webrd01.is.autonavi.com/appmaptile?langzh_cnsize1scale1style8x{x}y{y}z{z}这个模板的意思很直白{z}是缩放级别{x}和{y}是瓦片行列号都是从左上角起算的Web墨卡托瓦片坐标和Cesium里默认的二维瓦片坐标完全一致。这里有一个很关键的细节高德官方的示例URL里子域名经常写成webrd0{s}这个{s}是给分布式服务器用的占位符Cesium里会自动替换成0到4的数字。但QGIS的xyz provider默认只处理{x}、{y}、{z}三个占位符不处理{s}。如果直接把带{s}的URL填进去高德服务器会返回错误图层自然加载不出来。解决办法很简单把webrd0{s}替换成固定子域比如webrd01或者写成webrd0、webrd1、webrd2都行实测都能用。这是一个出镜率极高的坑几乎每个加载高德瓦片的人都会踩一次。2.3 为什么选xyz而不是WMS/WMTS加载在线底图理论上还有WMS和WMTS两种方式。网上也常见用WMS加载天地图的教程。但对比之后我个人强烈推荐优先用xyz。WMS是动态渲染的服务每次请求服务器都要现场切图速度快不了。WMTS虽然做了切片缓存但配置复杂需要先请求能力文档拿到TILEMATRIX的层级定义然后拼接GetTile请求调试成本高。而xyz格式直接把切片当作“约定俗成的文件路径”URL短、请求快、容易被各种缓存组件接管是Web GIS事实上的标准。更重要的是Cesium里的二维瓦片底图绝大多数就是XYZ来源。既然源本身就是XYZ那在QGIS这边也用XYZ加载天然吻合不会出现坐标偏移和层级对不上的问题。当然如果遇到某些只提供WMS服务的内部地图服务那还是得用WMS这个没有绝对的对错。3. 动手实操用QgsRasterLayer加载常见瓦片源3.1 环境准备推荐用QGIS 3.16以上版本LTR版本最稳我长期用3.22和3.28都没问题。操作之前先确认一件事QGIS版本必须能直接新建XYZ瓦片图层。QGIS 3.x都支持。验证方式有两种如果你只是想快速测试直接打开QGIS在浏览器面板里找到“XYZ Tiles”分类右键“新建连接”把URL填进去点击“添加”就能看到效果。这种方法不需要写任何代码适合先验证图源是否可用。如果你是做二次开发那就需要准备开发环境。Qt QGIS C SDK的方式比较传统需要下载OSGeo4W安装qgis-devel相关组件。如果只是写插件或者脚本直接用QGIS自带的Python控制台或者外部Python环境也可以QGIS安装目录里内置了Python 3.x和PyQt。这篇笔记里的代码以Python控制台为主因为方便读者最快速复现后面也会给出C的核心代码片段。3.2 Python快速验证代码打开QGIS的Python控制台插件菜单或CtrlAltP直接输入下面这段代码url https://webrd01.is.autonavi.com/appmaptile?langzh_cnsize1scale1style8x{x}y{y}z{z} name 高德道路 layer QgsRasterLayer(url, name, xyz) if not layer.isValid(): print(图层创建失败请检查URL) else: layer.setCrs(QgsCoordinateReferenceSystem(EPSG:3857)) QgsProject.instance().addMapLayer(layer) print(加载成功)这段代码至少做了四件事用URL模板创建一个QgsRasterLayer指定provider为xyz把图层坐标系设置为EPSG:3857把图层添加到当前工程。有一点要说明QgsRasterLayer的setCrs虽然写了但xyz provider通常会在瓦片请求过程中自行确定坐标系因为瓦片服务默认就是Web墨卡托。这里显式设置CRS是为了保险尤其后面要叠加其他图层时保证视图能正确定位。如果执行成功QGIS画布上应该能看到高德底图。如果发现黑屏或者什么都没有大概率是URL有问题或者网络无法访问后面的“常见问题”部分会详细排查。3.3 C代码封装示例在C项目里核心逻辑和Python几乎一致只是多了C的语法框架。下面是一段简化的示例#include QString #include QgsRasterLayer.h #include QgsProject.h #include QgsCoordinateReferenceSystem.h QString url QStringLiteral(https://webrd01.is.autonavi.com/appmaptile?langzh_cnsize1scale1style8x{x}y{y}z{z}); QString name QStringLiteral(高德道路); QgsRasterLayer* layer new QgsRasterLayer(url, name, QStringLiteral(xyz)); if (layer-isValid()) { layer-setCrs(QgsCoordinateReferenceSystem::fromEpsgId(3857)); QgsProject::instance()-addMapLayer(layer); } else { qWarning() 加载高德瓦片失败; }需要注意几点QgsRasterLayer构造函数里的第三个参数必须是大写还是小写这里的“xyz”是provider名称必须是小写。很多从旧项目迁移过来的代码里可能写成“wms”或者其他那就会导致图层无效。另外这行代码在工程里运行时需要确保QGIS的库路径已经正确配置否则会出现找不到QgsRasterLayer头文件或者链接失败的情况。常见的做法是在项目pro文件里追加CONFIG qgis LIBS -lqgis_core具体路径要根据你的QGIS开发版安装目录调整这里不展开。3.4 常用的国内可用瓦片源整理我把自己在项目中验证过、稳定可用的几个瓦片源整理如下全部都是公开的在线服务直接在QGIS里测试即可。图源名称URL模板说明高德道路https://webrd01.is.autonavi.com/appmaptile?langzh_cnsize1scale1style8x{x}y{y}z{z}国内道路标注全数据更新快高德影像https://webst01.is.autonavi.com/appmaptile?style6x{x}y{y}z{z}卫星影像叠加道路更好用高德影像标注https://webst01.is.autonavi.com/appmaptile?style8x{x}y{y}z{z}影像上的道路和地名标注OSMhttps://tile.openstreetmap.org/{z}/{x}/{y}.png全球道路样式简洁天地图矢量详见天地图官网申请key需要配置key数据规范有人可能会问为什么没有放Cesium官方示例里常用的那些源原因很简单有些源在国外服务器上国内访问不稳定我在实际项目里吃过亏所以只推荐稳定可用的。天地图需要注册获得tk参数加载方式可以在URL后面拼接例如http://t0.tianditu.gov.cn/DataServer?Tvec_wx{x}y{y}l{z}tk你的key这个URL里用的是l而不是{z}但QGIS的xyz provider只识别{z}所以不能直接套用。折中的办法是使用天地图的WMTS接口把TILEMATRIX参数手动对应到{z}或者用QGIS内置的“XYZ Tiles”向导来添加。天地图这部分对新手不太友好建议先拿高德练手。4. 投影与坐标系的坑4.1 EPSG:3857和CGCS2000别搞混Cesium里的二维瓦片默认是Web Mercator投影对应的EPSG代码是3857。国内的天地图、高德地图本质上也是Web Mercator只是在显示的时候做了一些坐标偏移处理GCJ-02加密这个偏移属于数据源的特性不是QGIS的问题。很多刚接触的人容易把EPSG:3857和CGCS2000EPSG:4490搞混。CGCS2000是我国法定的大地坐标系平面投影常用高斯-克吕格和Web Mercator完全是两套东西。如果你把瓦片底图当作CGCS2000来叠加那影像和矢量数据会位移几百米甚至更远而且这种位移不是整体平移是随位置变化的几乎没法手动纠正。反过来理解Cesium里的二维地图坐标和EPSG:3857是同一套坐标系所以QgsRasterLayer加载之后默认的显示坐标系就是3857。如果你的工程是其他坐标系也不要直接在图层属性里乱改正确的做法是让QGIS对瓦片图层做动态投影而不是修改数据本身的坐标系。4.2 QgsRasterLayer中的坐标解析流程QGIS的渲染管线大致是先看图层的原始CRS然后根据工程CRS做动态重投影最后显示在画布上。对于xyz瓦片原始CRS就是Web Mercator这个信息provider能自动识别不需要人工干预。但在某些特殊场景下比如加载自定义瓦片服务时provider可能不知道坐标系这时QgsRasterLayer会显示CRS未知。如果遇到这种情况叠加矢量图层出现偏移就先检查图层的CRS字段是否为3857不是就手动指定。手动指定可以用代码layer-setCrs(QgsCoordinateReferenceSystem::fromEpsgId(3857));也可在QGIS界面里右键图层选择“图层属性”里的“源”选项卡在“假设坐标系”里选择WGS 84 / Pseudo-Mercator。4.3 叠加矢量数据时的投影策略在实际项目中瓦片底图通常是“被叠加的底图”矢量数据才是主角。如果矢量数据源是CGCS2000或者国家2000的投影坐标那QGIS会在显示时自动把它们重投影到画布坐标系上。这里有个常见误区有人为了让矢量数据和瓦片“对齐”把所有矢量都转成3857。这在展示场景下可以但如果涉及面积计算、拓扑分析Web Mercator的变形不能忽视在高纬度区域尤其明显。我的做法是底图用3857原始矢量数据保持它原有的坐标系只在工程层面设置成3857或者按需切换到目标坐标系。5. 常见问题与排查实录5.1 高频症状速查表下面这些问题是这几年我在技术社区、项目现场见到的最高频情况整理成表格方便对照排查。症状可能原因解决办法图层创建失败isValid()返回falseURL模板错误、XYZ provider不支持、网络不通先在浏览器里访问URL模板手动替换x/y/z测试画布黑屏但图层已添加瓦片服务返回错误、并发请求被限制检查URL是否带{s}占位符固定子域后重试影像和道路标注错位图源本身就是不同图层叠加时序错误先加载影像图再加载影像标注图层与矢量数据偏移CRS设置不对或图源是GCJ-02加密确认瓦片底层是3857矢量数据确认坐标系加载慢、卡顿请求并发太高、网络不稳定加磁盘缓存或降低默认加载层级高德URL有时能加载有时不能子域服务器负载均衡问题固定到webrd01或者webst01子域5.2 排查细节从URL开始遇到加载问题第一步不是在QGIS里瞎试而是先在浏览器里手动验证URL。比如把下面的地址粘到浏览器https://webrd01.is.autonavi.com/appmaptile?langzh_cnsize1scale1style8x231y103z9如果浏览器能显示瓦片图片说明图源可用问题在QGIS这边的配置。如果浏览器也打不开那就是URL拼写错误、图源失效或者网络访问不了。浏览器能访问但QGIS不行常见原因就是{s}占位符。碰到高德这种带子域占位符的URL老老实实替换成固定子域。我见过一个项目同事把带{s}的URL直接挂到QGIS服务器上跑了一个月地图时好时坏最后定位到就是子域轮询问题改了之后就再也没犯过。5.3 天地图加载失败的处理天地图的瓦片URL通常带tk参数这个key有域名绑定限制而且申请需要一定审核时间。如果你在QGIS里填了天地图的URL但加载不出来先确认key是否有效然后确认URL中的参数名是否写对。我用的是DataServer接口实测在QGIS里需要把l参数换成z才能识别因为xyz provider只替换{z}。原来的URL里如果写的是l{z}那QGIS会把文字“l{z}”原样发出去天地图不认参数肯定报错。正确的做法是手动构造一个中间层或者直接用QGIS的“WMS/WMTS”方式加载天地图虽然麻烦一点但至少稳定。6. 开发过程中的几点经验写QGSRasterLayer加载瓦片代码量不大但要做到生产可用有几个细节值得说一说。第一缓存一定要开。QGIS提供了磁盘缓存和内存缓存默认情况下有些版本没开全。我的做法是在初始化工程时把瓦片缓存目录指到一个大分区同时把缓存容量调大。这样第二次打开工程时瓦片从本地读速度快一个量级。第二图源不要硬编码在代码里。把URL模板、坐标系、最大缩放级别、图层名称放到配置文件里后续换图源只改配置不动代码。我在C项目里用了一个简单JSON配置文件启动时读出一个QgsRasterLayer列表统一加载省了大量重复代码。第三在线瓦片和离线瓦片要有一个抽象层。有些项目现场是内网环境无法访问公网图源。我的做法是把常用图源提前下载到本地MBTiles文件代码里写一个逻辑如果检测到公网无法访问就自动切换到MBTiles本地数据源。这样在外业和内业之间切换代码逻辑不用改。第四关于瓦片加载层级如果遇到“一放大就空白”的情况先查图源支持的最大缩放级别。高德道路一般到18级天地图到18级但不同区域可能略有差异。QGIS里设置XYZ图层的最大缩放级别时不要超过图源实际支持的级别否则超出范围的请求会返回空图。第五内存释放问题。C环境下new出来的QgsRasterLayer指针在QGIS里注册给QgsProject之后通常不需要手动delete工程销毁时会统一清理。但如果你只是临时创建图层做测试不加入工程一定要记得手动清理否则长时间运行就会出现内存泄漏。这个坑在长时间运行的QGIS Server插件里尤其致命。最后说一个和Cesium联动的经验如果在你的系统里前端Cesium和桌面QGIS用的是同一套图源URL模板可以把这份模板统一抽到一个共享配置中心前端和后端各读各的。这样当某个图源出现故障需要整体切换时只改配置中心一处整个系统就同时切换绝不会出现前端一个底图、桌面端另一个底图的分裂状态。这套方案我已经在两个正式项目里落地过一次是水利行业的GIS数据生产一次是城市规划展示平台的桌面端工具链效果都很稳定。QgsRasterLayer这个类看着不起眼但一旦把它的XYZ加载机制吃透你会发现Cesium和QGIS之间的底图鸿沟其实比想象中小得多。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →