三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

OpenLayers WMTS加载天地图EPSG:4326瓦片与BD09坐标转换实战(07)

OpenLayers WMTS加载天地图EPSG:4326瓦片与BD09坐标转换实战(07)

天地图同时提供EPSG:4326(经纬度投影)和EPSG:3857(Web墨卡托投影)两种瓦片服务。使用EPSG:4326投影时,OpenLayers需要配置自定义WMTS数据源和瓦片网格。本文基于实际项目代码,详细讲解如何在Vue3 + OpenLayers项目中加载天地图EPSG:4326瓦片,并将百度BD09坐标正确投放到底图上。


一、EPSG:4326 vs EPSG:3857:如何选择?

1.1 两种投影的区别

特性EPSG:4326EPSG:3857
坐标类型经纬度(度)墨卡托(米)
坐标范围[-180, -90] ~ [180, 90]约[-20037508, -20037508] ~ [20037508, 20037508]
形状变形高纬度拉伸等角投影,面积变形
瓦片矩阵集matrixSet: "c"matrixSet: "w"
URL标识vec_c/cva_cvec_w/cia_w

1.2 选择建议

  • 数据源为经纬度:使用EPSG:4326,坐标无需额外转换
  • 与标准Web地图对接:使用EPSG:3857,兼容性更好
  • 需要精确面积/距离计算:使用EPSG:4326

二、EPSG:4326坐标转换

2.1 转换链路

天地图EPSG:4326瓦片基于标准WGS84坐标系,BD09坐标的转换链路非常简洁:

BD09(百度经纬度)→ EPSG:4326(WGS84经纬度)

只需一步转换,无需再转换为墨卡托坐标。

2.2 转换实现

importgcoordfrom'gcoord'// BD09 → EPSG:4326(WGS84经纬度)constbd09ToEPSG4326=(coord)=>gcoord.transform(coord,gcoord.BD09,gcoord.EPSG4326)// 使用示例constcenter=bd09ToEPSG4326([116.404,39.915])// 结果: [116.3707xx, 39.8853xx](EPSG:4326经纬度,单位:度)

关键区别:EPSG:4326模式下,地图中心点直接使用经纬度坐标(单位:度),而EPSG:3857模式下使用墨卡托坐标(单位:米)。


三、WMTS数据源封装

3.1 为什么需要自定义WMTS源?

天地图EPSG:4326瓦片使用WMTS协议,与标准XYZ瓦片不同:

  • WMTS需要指定瓦片矩阵集(matrixSet)
  • 需要根据投影计算分辨率数组
  • 需要配置瓦片网格(TileGrid)

OpenLayers的ol/source/WMTS需要完整的瓦片网格配置,不能像XYZ那样简单使用URL模板。

3.2 createWMTSSource封装

import{WMTS}from'ol/source'import{getasolProjGet}from'ol/proj'import{getWidth}from'ol/extent'importWMTSTileGridfrom'ol/tilegrid/WMTS'/** * 创建天地图WMTS数据源 * @param {Object} params - 配置参数 * @param {string} params.projection - 坐标投影,默认 'EPSG:3857' * @param {number} params.maxZoom - 最大缩放级别,默认 23 * @param {Array} params.origin - 坐标原点,默认 [-180.0, 90.0] * @param {number} params.tileSize - 瓦片大小,默认 256 * @returns {WMTS} WMTS数据源 */constcreateWMTSSource=(params)=>{const{projection:projection='EPSG:3857',maxZoom:maxZoom=23,origin:origin=[-180.0,90.0],tileSize:tileSize=256,...options}=params// 获取投影对象letProjection=olProjGet(projection)constprojectionExtent=Projection.getExtent()// 计算投影范围宽度与瓦片大小的比值constsize=getWidth(projectionExtent)/tileSize// 生成分辨率数组和矩阵IDconstresolutions=[]constmatrixIds=[]for(leti=1;i<=maxZoom;i++){// 每一级分辨率 = 上一级分辨率 / 2resolutions.push(size/Math.pow(2,i))// 矩阵ID必须从 '1' 开始,且为字符串格式matrixIds.push(String(i))}// 创建 WMTS 数据源returnnewWMTS({...options,projection:projection,tileGrid:newWMTSTileGrid({tileSize:tileSize,origin:origin,resolutions:resolutions,matrixIds:matrixIds,maxZoom:maxZoom})})}

3.3 分辨率计算原理

天地图EPSG:4326的瓦片分辨率计算基于投影范围:

projectionExtent = [-180, -90, 180, 90] // EPSG:4326范围 size = getWidth(extent) / tileSize = 360 / 256 = 1.40625 第1级分辨率: 1.40625 / 2^1 = 0.703125 第2级分辨率: 1.40625 / 2^2 = 0.3515625 ... 第18级分辨率: 1.40625 / 2^18 ≈ 0.00000536

每一级分辨率是上一级的一半,缩放越高级别,分辨率越精细。


四、EPSG:4326 vs EPSG:3857 配置对比

4.1 WMTS参数差异

参数EPSG:4326EPSG:3857
projection"EPSG:4326""EPSG:3857"
matrixSet"c""w"
origin[-180.0, 90.0][0, 0](默认)
URL图层vec_c/cva_cvec_w/cia_w
数据源类型ol/source/WMTSol/source/XYZ

4.2 地图视图差异

// EPSG:4326 模式:中心点使用经纬度(度)view:newView({center:[116.404,39.915],// 直接使用经纬度zoom:10,projection:'EPSG:4326'})// EPSG:3857 模式:中心点使用墨卡托(米)view:newView({center:[12958175,4862293],// 墨卡托坐标zoom:10,projection:'EPSG:3857'})

4.3 坐标转换差异

// EPSG:4326:BD09 → EPSG:4326(一步转换,结果为经纬度)constbd09To4326=(coord)=>gcoord.transform(coord,gcoord.BD09,gcoord.EPSG4326)// EPSG:3857:BD09 → EPSG:4326 → EPSG:3857(两步转换,结果为墨卡托)constbd09To3857=(coord)=>{constwgs84=gcoord.transform(coord,gcoord.BD09,gcoord.EPSG4326)returngcoord.transform(wgs84,gcoord.EPSG4326,gcoord.EPSG3857)}

五、完整Vue3组件实现

以下是基于项目ol-bd-tdtEPSG4326.vue的完整实现:

<template> <div class="wh100" ref="olMapRef"></div> </template> <script setup> import {ref, onMounted} from 'vue' import Map from 'ol/Map' import View from 'ol/View' import TileLayer from 'ol/layer/Tile' import VectorLayer from "ol/layer/Vector"; import VectorSource from "ol/source/Vector"; import {GeoJSON} from "ol/format"; import gcoord from 'gcoord' import {Circle as CircleStyle, Fill, Stroke, Style} from "ol/style"; import {WMTS} from "ol/source"; import {get as olProjGet} from "ol/proj"; import {getWidth} from "ol/extent"; import WMTSTileGrid from "ol/tilegrid/WMTS"; // 百度坐标(BD09) → EPSG:4326(一步转换) const bd09ToEPSG4326 = (coord) => gcoord.transform(coord, gcoord.BD09, gcoord.EPSG4326) const bd09ToMercator = (coord) => bd09ToEPSG4326(coord) const olMapRef = ref(null) /** * 创建天地图WMTS数据源 * 根据投影计算分辨率数组和瓦片网格 */ const createWMTSSource = (params) => { const { projection: projection = 'EPSG:3857', maxZoom: maxZoom = 23, origin: origin = [-180.0, 90.0], tileSize: tileSize = 256, ...options } = params let Projection = olProjGet(projection) const projectionExtent = Projection.getExtent(); const size = getWidth(projectionExtent) / tileSize; const resolutions = []; const matrixIds = []; for (let i = 1; i <= maxZoom; i++) { resolutions.push(size / Math.pow(2, i)); matrixIds.push(String(i)); } return new WMTS({ ...options, projection: projection, tileGrid: new WMTSTileGrid({ tileSize: tileSize, origin: origin, resolutions: resolutions, matrixIds: matrixIds, maxZoom: maxZoom }) }); } onMounted(() => { // BD09 → EPSG:4326,中心点直接使用经纬度 const center = bd09ToMercator([116.404, 39.915]) const map = new Map({ target: olMapRef.value, layers: [ // 矢量底图(EPSG:4326) new TileLayer({ source: createWMTSSource({ url: 'https://t0.tianditu.gov.cn/vec_c/wmts?LAYER=vec&tk=你的密钥', tileSize: 256, crossOrigin: "anonymous", maxZoom: 18, origin: [-180.0, 90.0], projection: "EPSG:4326", matrixSet: "c", style: "default", format: "image/png", VERSION: "1.0.0", REQUEST: "GetTile", SERVICE: "WMTS", TILEMATRIX: "{z}", TILEROW: "{y}", TILECOL: "{x}" }) }), // 矢量注记(EPSG:4326) new TileLayer({ source: createWMTSSource({ url: 'https://t0.tianditu.gov.cn/cva_c/wmts?LAYER=cva&tk=你的密钥', tileSize: 256, crossOrigin: "anonymous", maxZoom: 18, origin: [-180.0, 90.0], projection: "EPSG:4326", matrixSet: "c", style: "default", format: "image/png", VERSION: "1.0.0", REQUEST: "GetTile", SERVICE: "WMTS", TILEMATRIX: "{z}", TILEROW: "{y}", TILECOL: "{x}" }) }) ], view: new View({ center: center, // 北京市中心(EPSG:4326经纬度) zoom: 10, projection: 'EPSG:4326' }) }) let geoJsonData = { "type": "FeatureCollection", "features": [ { "type": "Feature", "id": 1, "properties": { "name": '', "level": 10, "zIndex": 200 }, "geometry": { "type": 'Point', "coordinates": center } } ] } // 测试 [116.404, 39.915] 坐标是否准确 const layer = new VectorLayer({ source: new VectorSource({ features: new GeoJSON({}).readFeatures(geoJsonData) }) }) layer.setStyle(new Style({ image: new CircleStyle({ radius: 5, fill: new Fill({ color: '#ff0000' }), stroke: new Stroke({ color: '#ff0000', width: 3 }) }), })) map.addLayer(layer) }) </script>


5.1 代码要点解析

1. 坐标转换极简

// 只需一步转换,结果直接是经纬度constcenter=bd09ToEPSG4326([116.404,39.915])// center ≈ [116.3707, 39.8853](单位:度)

EPSG:4326模式下,坐标就是经纬度,无需再做墨卡托投影转换。

2. WMTS数据源配置

createWMTSSource({projection:"EPSG:4326",matrixSet:"c",// 关键:EPSG:4326使用"c"矩阵集origin:[-180.0,90.0],// EPSG:4326的坐标原点maxZoom:18})

3. 地图视图使用经纬度

view:newView({center:center,// 直接使用EPSG:4326经纬度坐标zoom:10,projection:'EPSG:4326'// 关键:视图投影为EPSG:4326})

六、天地图EPSG:4326图层配置速查

6.1 矢量地图

// 底图{url:'https://t0.tianditu.gov.cn/vec_c/wmts?LAYER=vec&tk=你的密钥',projection:"EPSG:4326",matrixSet:"c"}// 注记{url:'https://t0.tianditu.gov.cn/cva_c/wmts?LAYER=cva&tk=你的密钥',projection:"EPSG:4326",matrixSet:"c"}

6.2 影像地图

// 底图{url:'https://t0.tianditu.gov.cn/img_c/wmts?LAYER=img&tk=你的密钥',projection:"EPSG:4326",matrixSet:"c"}// 注记{url:'https://t0.tianditu.gov.cn/cia_c/wmts?LAYER=cia&tk=你的密钥',projection:"EPSG:4326",matrixSet:"c"}

6.3 URL图层代码速查

图层EPSG:4326EPSG:3857
矢量底图vec_cvec_w
矢量注记cva_ccia_w
影像底图img_cimg_w
影像注记cia_ccta_w

七、常见问题与排查

7.1 瓦片加载空白

原因createWMTSSource中未正确配置EPSG:4326参数。

检查项

  1. projection是否设置为"EPSG:4326"
  2. matrixSet是否设置为"c"
  3. origin是否设置为[-180.0, 90.0]
  4. URL中是否使用了_c后缀(如vec_c

7.2 地图显示但坐标偏移

原因:视图投影与瓦片投影不匹配。

// 错误:视图使用EPSG:3857,但瓦片是EPSG:4326view:newView({projection:'EPSG:3857'// ❌})// 正确:视图投影必须与瓦片投影一致view:newView({projection:'EPSG:4326'// ✓})

7.3 缩放级别不正确

原因maxZoom配置与实际瓦片支持级别不匹配。

天地图EPSG:4326瓦片支持0-18级缩放,建议设置maxZoom: 18

7.4 矩阵ID错误

原因matrixIds必须从"1"开始,且为字符串格式。

// 正确:从"1"开始的字符串数组matrixIds.push(String(i))// ["1", "2", "3", ...]// 错误:从0开始或使用数字类型matrixIds.push(i)// ❌

八、总结

8.1 核心要点

步骤说明
1. 坐标转换BD09 → EPSG:4326,一步转换,结果为经纬度
2. WMTS数据源使用createWMTSSource封装,配置投影和瓦片网格
3. 矩阵集EPSG:4326使用matrixSet: "c"
4. 视图投影必须设置为projection: 'EPSG:4326'
5. 图层叠加底图(vec_c)+ 注记(cva_c)需分别加载

8.2 三种底图方案对比

方案坐标转换数据源类型视图投影复杂度
天地图EPSG:3857BD09 → 4326 → 3857XYZEPSG:3857
天地图EPSG:4326BD09 → 4326WMTSEPSG:4326
高德瓦片BD09 → GCJ02 → 手写3857XYZEPSG:3857

8.3 坐标转换速查

importgcoordfrom'gcoord'// 百度经纬度 → 天地图EPSG:4326(一步)constbd09ToTdt4326=(coord)=>gcoord.transform(coord,gcoord.BD09,gcoord.EPSG4326)// 百度经纬度 → 天地图EPSG:3857(两步)constbd09ToTdt3857=(coord)=>{constwgs84=gcoord.transform(coord,gcoord.BD09,gcoord.EPSG4326)returngcoord.transform(wgs84,gcoord.EPSG4326,gcoord.EPSG3857)}

更新日期:2026年8月
调试版本:OpenLayers 10.9.0

← 返回列表