Leaflet加载WMTS服务踩坑全记录:从‘不李姐’到成功叠加天地图与CGCS2000图层
第一次尝试在Leaflet中加载WMTS服务时,那种"明明照着文档做却死活不显示"的挫败感至今记忆犹新。特别是当需要处理非标准坐标系(如CGCS2000)时,官方文档的示例代码仿佛成了摆设,各种未提及的细节问题接踵而至。本文将还原一个真实项目中的完整踩坑历程,重点不是给你一段可以无脑复制的代码,而是带你理解那些官方文档没讲清楚的关键点。
1. 坐标系:一切问题的起点
Leaflet默认只支持EPSG:3857(Web墨卡托)和EPSG:4326(WGS84经纬度)两种坐标系,这在实际项目中远远不够。当我们需要使用CGCS2000坐标系(EPSG:4490)时,必须自定义CRS(Coordinate Reference System)。
1.1 自定义CRS的陷阱
官方推荐使用proj4leaflet插件来定义自定义坐标系,但实际操作中你会发现:
// 理论上应该可行的proj4leaflet方案(但实际可能不工作) L.Proj.CRS('EPSG:4490', { origin: [-180, 90], resolutions: [ // 分辨率数组 ], bounds: L.bounds([-180, -90], [180, 90]) });而实践中,下面这种看似"野路子"的方案反而更可靠:
// 实际可用的自定义CRS方案 L.CRS.CustomEPSG4490 = L.extend({}, L.CRS.Earth, { code: 'EPSG:4490', projection: L.Projection.LonLat, transformation: new L.Transformation(1 / 180, 1, -1 / 180, 0.5), scale: function (zoom) { return 256 * Math.pow(2, zoom - 1); } });关键差异在于:
- transformation参数:决定了坐标如何映射到屏幕像素
- scale函数:控制不同缩放级别下的瓦片显示比例
- projection:指定使用经纬度投影而非墨卡托
2. 天地图服务的玄机
天地图WMTS服务有两个容易混淆的版本:
| 参数 | 地理坐标系版本 | 投影坐标系版本 |
|---|---|---|
| 服务地址 | img_c | img_w |
| 矩阵集 | c | w |
| 适用CRS | EPSG:4490 | EPSG:3857 |
一个典型的坑是:当你使用自定义的EPSG:4490 CRS时,却错误地请求了img_w服务,结果必然是空白一片。
正确的图层初始化应该是:
const img_c = L.tileLayer( `http://t0.tianditu.gov.cn/img_c/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=c&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${tdtKey}` );3. WMTS插件的水有多深
加载自定义WMTS服务时,leaflet.wmts插件是必备工具,但这里藏着几个大坑:
3.1 插件版本差异
- 官网下载版:可能需要手动修改源码才能支持非标准CRS
- NPM安装版:通常已经包含必要的补丁
# 推荐安装方式 npm install leaflet.wmts3.2 矩阵集配置
CGCS2000坐标系需要明确定义矩阵ID:
let matrixIds = []; for (let i = 0; i < 22; ++i) { matrixIds[i] = { identifier: "" + i, topLeftCorner: new L.LatLng(90, -180) }; }3.3 图层参数解析
WMTS图层的每个参数都至关重要:
let wmtsLayer = new L.TileLayer.WMTS(url, { layer: '图层名称', style: '', tilematrixSet: "CGCS2000", format: "image/png", crs: L.CRS.CustomEPSG4490, matrixIds: matrixIds });4. 完整实现方案
将所有碎片整合起来,一个完整的Vue组件实现如下:
<template> <div id="map"></div> </template> <script setup> import { onMounted } from 'vue'; import L from "leaflet"; import "leaflet/dist/leaflet.css"; import 'leaflet.wmts'; const tdtKey = "你的天地图密钥"; // 自定义CRS L.CRS.CustomEPSG4490 = L.extend({}, L.CRS.Earth, { code: 'EPSG:4490', projection: L.Projection.LonLat, transformation: new L.Transformation(1 / 180, 1, -1 / 180, 0.5), scale: function (zoom) { return 256 * Math.pow(2, zoom - 1); } }); // 准备WMTS矩阵集 let matrixIds = []; for (let i = 0; i < 22; ++i) { matrixIds[i] = { identifier: "" + i, topLeftCorner: new L.LatLng(90, -180) }; } const initMap = () => { // 天地图底图 const img_c = L.tileLayer( `http://t0.tianditu.gov.cn/img_c/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=c&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${tdtKey}` ); const cia_c = L.tileLayer( `http://t0.tianditu.gov.cn/cia_c/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cia&STYLE=default&TILEMATRIXSET=c&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${tdtKey}` ); const layers = L.layerGroup([img_c, cia_c]); // 初始化地图 let map = L.map('map', { center: [24.889157, 102.839443], zoom: 6, maxZoom: 18, minZoom: 0, layers: [layers], crs: L.CRS.CustomEPSG4490 }); // 添加WMTS图层 let url = 'http://your-service/wmts'; let wmtsLayer = new L.TileLayer.WMTS(url, { layer: 'your-layer', style: '', tilematrixSet: "CGCS2000", format: "image/png", crs: L.CRS.CustomEPSG4490, matrixIds: matrixIds }); map.addLayer(wmtsLayer); }; onMounted(() => { initMap(); }); </script> <style scoped> #map { height: 100vh; width: 100%; } </style>5. 调试技巧与常见问题
当图层无法显示时,按以下步骤排查:
- 检查控制台错误:查看是否有CORS或404错误
- 验证WMTS请求URL:直接在浏览器地址栏测试
- 坐标系一致性检查:
- 确保CRS与WMTS服务匹配
- 确认tilematrixSet参数正确
- 缩放级别问题:
- 检查minZoom/maxZoom设置
- 确认矩阵ID与缩放级别对应
特别提醒:天地图服务需要有效的密钥,且免费版有访问频率限制
经过这些折腾,最大的体会是:Leaflet的灵活性是把双刃剑。它给了你足够的自由去实现各种需求,但也意味着你需要自己处理很多底层细节。当标准方案不奏效时,社区的各种非官方解决方案往往能救命,这就是开源生态的魅力所在。