Leaflet 实现墨卡托投影与经纬度投影切换(EPSG:3857 ↔ EPSG:4326)
前言
在使用 Leaflet 开发 Web GIS 应用时,地图显示的核心不仅是瓦片图层,还包括地图所使用的坐标参考系统(CRS)。
常见的两种坐标体系是:
| 坐标体系 | 说明 | 典型瓦片 |
|---|---|---|
| EPSG:3857 | Web 墨卡托投影,平面坐标,单位通常为米 | OSM、高德、谷歌地图瓦片 |
| EPSG:4326 | WGS84 经纬度地理坐标系,单位为度 | 部分经纬度瓦片、GPS 原始坐标 |
Leaflet 虽然支持EPSG:3857和EPSG:4326,但地图实例创建后不能直接切换 CRS。正确的做法是:
销毁旧地图 → 保存视图状态 → 使用新 CRS 重建地图 → 更换对应瓦片 → 重新添加矢量图层。
一、基础概念
1. EPSG:3857 Web 墨卡托
Web 墨卡托是互联网地图中最常用的投影方式。
- 坐标形式:平面坐标
(x, y) - 单位:米
- 适合:在线地图瓦片展示
- Leaflet 默认:
L.CRS.EPSG3857
2. EPSG:4326 WGS84 经纬度
经纬度坐标系更适合表达真实地理位置。
- 坐标形式:
(纬度, 经度) - 单位:度
- 适合:GPS 数据、地理数据原始表达
- Leaflet 支持:
L.CRS.EPSG4326
二、核心注意事项
瓦片必须与 CRS 匹配
这是最容易出错的地方。
| CRS | 瓦片类型 | 说明 |
|---|---|---|
| EPSG:3857 | 墨卡托瓦片 | 使用标准{z}/{x}/{y}墨卡托切片 |
| EPSG:4326 | 经纬度瓦片 | 切片规则与墨卡托不同,不能混用 |
如果瓦片与 CRS 不匹配,地图会出现:
- 偏移
- 缩放异常
- 网格错乱
- 矢量点位置错误
三、Leaflet 切换投影实现
1. HTML 结构
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8" /> <title>Leaflet CRS Switch Demo</title> <link rel="stylesheet" href="[https://unpkg.com/leaflet@1.9.4/dist/leaflet.css](https://unpkg.com/leaflet@1.9.4/dist/leaflet.css)" /> <style> html, body { margin: 0; padding: 0; height: 100%; font-family: "Microsoft YaHei", Arial, sans-serif; } #map { width: 100%; height: 600px; background: #f5f5f5; } .btn-group { padding: 12px; background: #282c34; color: #fff; display: flex; gap: 12px; align-items: center; } button { padding: 8px 16px; border: none; border-radius: 4px; background: #61afef; color: #fff; cursor: pointer; font-size: 14px; } button:hover { background: #528bcc; } .current-crs { margin-left: auto; font-size: 14px; color: #98c379; } </style> </head> <body> <div class="btn-group"> <button onclick="switchCrs('EPSG3857')">切换墨卡托 EPSG:3857</button> <button onclick="switchCrs('EPSG4326')">切换经纬度 EPSG:4326</button> <span class="current-crs">当前 CRS:<span id="currentCrsText">EPSG:3857</span></span> </div> <div id="map"></div> <script src="[https://unpkg.com/leaflet@1.9.4/dist/leaflet.js](https://unpkg.com/leaflet@1.9.4/dist/leaflet.js)"></script> <script> let map = null; let currentCRS = "EPSG3857"; // 默认视图:北京 let viewState = { center: [39.9042, 116.4074], zoom: 10 }; // 矢量数据 const markerData = [ { latlng: [39.9042, 116.4074], name: "北京" }, { latlng: [31.2304, 121.4737], name: "上海" }, { latlng: [23.1291, 113.2644], name: "广州" } ]; function createMap(crsName) { // 1. 销毁旧地图 if (map) { viewState.center = map.getCenter(); viewState.zoom = map.getZoom(); map.remove(); map = null; } // 2. 选择 CRS const crs = crsName === "EPSG3857" ? L.CRS.EPSG3857 : L.CRS.EPSG4326; currentCRS = crsName; document.getElementById("currentCrsText").textContent = currentCRS; // 3. 创建新地图 map = L.map('map', { crs: crs, center: viewState.center, zoom: viewState.zoom }); // 4. 添加对应瓦片 let tileLayer; if (crsName === "EPSG3857") { tileLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors', maxZoom: 19 }); } else { // 注意:EPSG:4326 需要使用经纬度瓦片 // 这里使用天地图经纬度影像底图作为示例,请替换为自己的 tk tileLayer = L.tileLayer( '[https://t0.tianditu.gov.cn/img_c/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=img&tileMatrixSet=c&TileMatrix={z}&TileRow={y}&TileCol={x}&tk=](https://t0.tianditu.gov.cn/img_c/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=img&tileMatrixSet=c&TileMatrix={z}&TileRow={y}&TileCol={x}&tk=)你的天地图key', { attribution: '天地图', maxZoom: 12 } ); } tileLayer.addTo(map); // 5. 重新添加矢量图层 markerData.forEach(item => { L.marker(item.latlng) .addTo(map) .bindPopup(item.name); }); } function switchCrs(type) { createMap(type); } // 初始化 createMap('EPSG3857'); </script> </body> </html>四、切换逻辑说明
Leaflet 投影切换的关键流程如下:
点击切换 CRS ↓ 记录当前地图中心点与缩放级别 ↓ 销毁旧地图实例 ↓ 根据新 CRS 创建地图 ↓ 加载对应投影瓦片 ↓ 重新添加 Marker、GeoJSON 等矢量数据核心代码:
function createMap(crsName) { if (map) { viewState.center = map.getCenter(); viewState.zoom = map.getZoom(); map.remove(); } const crs = crsName === "EPSG3857" ? L.CRS.EPSG3857 : L.CRS.EPSG4326; map = L.map('map', { crs: crs, center: viewState.center, zoom: viewState.zoom }); // 加载对应瓦片 ... }五、矢量数据是否需要手动转换?
不需要。
Leaflet 的矢量坐标统一使用WGS84 经纬度[lat, lng]。
L.marker([39.9042, 116.4074]).addTo(map);无论当前地图是EPSG:3857还是EPSG:4326,Leaflet 都会根据地图 CRS 自动转换。
所以:
- Marker:直接使用
[lat, lng] - Polyline:直接使用经纬度数组
- GeoJSON:直接传入标准 WGS84 坐标
六、坐标手动互转
如果只是想做坐标转换,不需要切换地图实例。
经纬度转 EPSG:3857 平面坐标
const latlng = L.latLng(39.9042, 116.4074); const point = L.CRS.EPSG3857.project(latlng); console.log("EPSG:3857 x:", point.x); console.log("EPSG:3857 y:", point.y);EPSG:3857 平面坐标转回经纬度
const point = L.point(12914824.806, 4833047.501); const latlng = L.CRS.EPSG3857.unproject(point); console.log("lat:", latlng.lat); console.log("lng:", latlng.lng);七、常见问题
1. 切换 CRS 后地图为什么偏移?
因为瓦片与 CRS 不匹配。
解决方式:
EPSG:3857使用墨卡托瓦片EPSG:4326使用经纬度瓦片
2. Marker 位置为什么不对?
大概率不是 Marker 坐标问题,而是底图瓦片投影不匹配。
3. 能不能只改 CRS 不重建地图?
不能。
Leaflet 地图实例的 CRS 在初始化时确定,创建后无法直接修改。
八、性能优化建议
如果切换频率较高,每次销毁重建会有一定开销,可以考虑:
- 双容器预加载两张地图
- 一个容器加载
EPSG:3857 - 一个容器加载
EPSG:4326 - 通过显示、隐藏切换
- 一个容器加载
- 矢量数据独立管理
- 将 GeoJSON、Marker、Polyline 数据统一保存
- 重建时批量添加
- 大数据量使用 GeoJSON 容器
- 避免频繁创建大量图形对象
- 使用
L.geoJSON()统一加载
总结
Leaflet 实现墨卡托与经纬度投影切换,核心不是复杂算法,而是遵循正确流程:
切换 CRS = 重建地图 + 更换对应瓦片 + 重新绑定矢量数据记住下面这张表即可:
| 操作 | 说明 |
|---|---|
EPSG:3857 | Web 墨卡托,适合互联网地图瓦片 |
EPSG:4326 | WGS84 经纬度,适合 GPS 和原始地理数据 |
| 切换方式 | 销毁旧地图,用新 CRS 重建 |
| 瓦片要求 | 必须与 CRS 对应 |
| 矢量坐标 | 统一使用[lat, lng] |