Vue3 + Leaflet 实战:搞定天地图与CGCS2000 WMTS服务叠加(附完整避坑代码)
在WebGIS开发领域,Vue3与Leaflet的结合正成为越来越多开发者的选择。这种组合既能享受Vue3响应式编程的便利,又能利用Leaflet轻量级地图库的灵活性。本文将带你深入探索如何在实际项目中实现天地图与CGCS2000坐标系的WMTS服务完美叠加,分享从项目搭建到最终实现的完整流程,特别针对Vue3的Composition API与Leaflet的集成提供详细解决方案。
1. 环境准备与基础配置
1.1 创建Vue3项目与安装依赖
首先确保你已经安装了最新版本的Node.js(建议16.x或以上)。使用Vite创建Vue3项目是一个高效的选择:
npm create vite@latest vue3-leaflet-demo --template vue
cd vue3-leaflet-demo
npm install leaflet leaflet.wmts
npm install --save-dev @types/leaflet
这里有几个关键点需要注意:
- 使用Vite而非传统Vue CLI,能获得更快的构建速度
- 同时安装leaflet和leaflet.wmts两个核心库
- 添加@types/leaflet以获得TypeScript支持(即使项目使用JavaScript也能获得更好的代码提示)
1.2 基础地图容器设置
在Vue组件中,我们需要先设置一个地图容器。创建一个
MapView.vue
组件:
<template>
<div id="map-container" ref="mapContainer"></div>
</template>
<style scoped>
#map-container {
width: 100%;
height: 100vh;
background-color: #f0f0f0;
}
</style>
重要提示 :Leaflet对CSS有严格要求,必须确保地图容器有明确的高度设置。常见问题包括:
- 容器高度为0导致地图不可见
- 父元素有overflow:hidden导致地图显示异常
- 未正确引入Leaflet的CSS文件
2. 自定义坐标系与天地图集成
2.1 定义CGCS2000坐标系(EPSG:4490)
Leaflet默认不支持CGCS2000坐标系,需要自定义扩展。在Vue3的setup函数中添加:
import L from 'leaflet';
// 自定义EPSG:4490坐标系
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函数控制不同缩放级别下的瓦片显示 -
code指定了EPSG代码,方便后续引用
2.2 加载天地图服务
天地图提供了多种服务类型,我们需要特别注意坐标系匹配问题。以下是地理坐标系(EPSG:4490)下的天地图WMTS服务配置:
const setupTianDiTuLayers = (map) => {
const tdtKey = '你的天地图密钥'; // 替换为实际申请的密钥
// 影像底图
const imgLayer = 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}`,
{
maxZoom: 18,
minZoom: 1,
attribution: '天地图影像服务'
}
);
// 注记层
const ciaLayer = 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}`,
{
maxZoom: 18,
minZoom: 1,
attribution: '天地图注记服务'
}
);
return L.layerGroup([imgLayer, ciaLayer]);
};
常见问题排查 :
-
图层不显示:检查密钥是否有效,URL中的
c是否与坐标系匹配 -
偏移问题:确认使用的是
img_c而非img_w服务 - 跨域问题:确保服务器配置了正确的CORS头
3. WMTS服务集成与图层管理
3.1 配置CGCS2000 WMTS服务
使用leaflet.wmts插件加载自定义WMTS服务:
import 'leaflet.wmts';
const setupWmtsLayer = (map) => {
// 生成矩阵集ID
const matrixIds = Array.from({length: 22}, (_, i) => ({
identifier: i.toString(),
topLeftCorner: new L.LatLng(90, -180)
}));
const wmtsLayer = new L.TileLayer.WMTS('http://your-wmts-service-url', {
layer: 'your-layer-name',
style: 'default',
tilematrixSet: 'CGCS2000',
format: 'image/png',
crs: L.CRS.CustomEPSG4490,
matrixIds: matrixIds,
opacity: 0.8
});
return wmtsLayer;
};
3.2 Vue3中的图层状态管理
在Vue3中,我们可以使用Composition API更好地管理图层状态:
import { ref, onMounted, onUnmounted } from 'vue';
export default {
setup() {
const map = ref(null);
const baseLayers = ref({});
const overlayLayers = ref({});
onMounted(() => {
map.value = L.map('map-container', {
crs: L.CRS.CustomEPSG4490,
center: [35, 110],
zoom: 5
});
// 初始化图层
baseLayers.value.tianditu = setupTianDiTuLayers(map.value);
overlayLayers.value.wmts = setupWmtsLayer(map.value);
// 添加默认图层
baseLayers.value.tianditu.addTo(map.value);
});
onUnmounted(() => {
if (map.value) {
map.value.remove();
}
});
return {
map,
baseLayers,
overlayLayers
};
}
};
这种实现方式带来了几个优势:
- 清晰的图层分类管理(底图与叠加层)
- 响应式的图层状态控制
- 自动的资源清理(组件卸载时移除地图)
4. 高级功能与性能优化
4.1 动态投影切换实现
在实际项目中,可能需要支持多种坐标系的切换。我们可以扩展之前的实现:
const crsOptions = {
'EPSG:4490': L.CRS.CustomEPSG4490,
'EPSG:3857': L.CRS.EPSG3857
};
const changeCRS = (crsCode) => {
if (!crsOptions[crsCode]) return;
const currentCenter = map.value.getCenter();
const currentZoom = map.value.getZoom();
map.value.remove();
map.value = L.map('map-container', {
crs: crsOptions[crsCode],
center: currentCenter,
zoom: currentZoom
});
// 重新添加图层
Object.values(baseLayers.value).forEach(layer => {
layer.addTo(map.value);
});
};
4.2 性能优化技巧
-
图层加载策略优化 :
-
使用
L.LayerGroup管理同类图层 - 实现按需加载(视口内加载)
-
对静态数据使用
L.GeoJSON而非WMTS
-
使用
-
内存管理 :
onUnmounted(() => { if (map.value) { map.value.eachLayer(layer => { map.value.removeLayer(layer); }); map.value.remove(); } }); -
事件处理优化 :
const handleMapClick = (e) => { // 使用节流函数防止频繁触发 console.log('Clicked at:', e.latlng); }; onMounted(() => { map.value.on('click', handleMapClick); }); onUnmounted(() => { if (map.value) { map.value.off('click', handleMapClick); } });
5. 完整实现与调试技巧
5.1 完整组件代码示例
<template>
<div class="map-container">
<div id="map" ref="mapEl"></div>
<div class="control-panel">
<button @click="toggleLayer('wmts')">切换WMTS图层</button>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import L from 'leaflet';
import 'leaflet.wmts';
import 'leaflet/dist/leaflet.css';
// 自定义坐标系
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);
}
});
const mapEl = ref(null);
const mapInstance = ref(null);
const layers = ref({
tianditu: null,
wmts: null
});
// 初始化天地图图层
const initTianDiTuLayer = () => {
const tdtKey = 'your-tianditu-key';
const imgLayer = 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}`,
{ maxZoom: 18, minZoom: 1 }
);
const ciaLayer = 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}`,
{ maxZoom: 18, minZoom: 1 }
);
return L.layerGroup([imgLayer, ciaLayer]);
};
// 初始化WMTS图层
const initWmtsLayer = () => {
const matrixIds = Array.from({length: 22}, (_, i) => ({
identifier: i.toString(),
topLeftCorner: new L.LatLng(90, -180)
}));
return new L.TileLayer.WMTS('http://your-wmts-service', {
layer: 'your-layer-name',
style: 'default',
tilematrixSet: 'CGCS2000',
format: 'image/png',
crs: L.CRS.CustomEPSG4490,
matrixIds: matrixIds,
opacity: 0.8
});
};
// 切换图层显示状态
const toggleLayer = (layerName) => {
if (!mapInstance.value || !layers.value[layerName]) return;
if (mapInstance.value.hasLayer(layers.value[layerName])) {
mapInstance.value.removeLayer(layers.value[layerName]);
} else {
layers.value[layerName].addTo(mapInstance.value);
}
};
onMounted(() => {
mapInstance.value = L.map(mapEl.value, {
crs: L.CRS.CustomEPSG4490,
center: [35, 110],
zoom: 5,
attributionControl: false
});
// 初始化图层
layers.value.tianditu = initTianDiTuLayer();
layers.value.wmts = initWmtsLayer();
// 添加默认图层
layers.value.tianditu.addTo(mapInstance.value);
});
onUnmounted(() => {
if (mapInstance.value) {
mapInstance.value.remove();
}
});
</script>
<style scoped>
.map-container {
position: relative;
width: 100%;
height: 100vh;
}
#map {
width: 100%;
height: 100%;
}
.control-panel {
position: absolute;
top: 10px;
right: 10px;
z-index: 1000;
background: white;
padding: 10px;
border-radius: 4px;
box-shadow: 0 0 10px rgba(0,0,0,0.2);
}
</style>
5.2 常见问题调试指南
-
图层不显示问题排查流程 :
- 检查浏览器开发者工具中的网络请求
- 确认WMTS服务URL是否正确
- 验证坐标系定义是否与服务匹配
- 检查天地图密钥是否有效
-
跨域问题解决方案 :
// 在vue.config.js中添加代理配置 module.exports = { devServer: { proxy: { '/geoserver': { target: 'http://your-geoserver-domain', changeOrigin: true } } } }; -
性能问题分析工具 :
-
使用Leaflet的
L.Util.stamp跟踪图层实例 - Chrome开发者工具的Performance面板记录性能
- 监控内存使用情况,防止泄漏
-
使用Leaflet的
&spm=1001.2101.3001.5002&articleId=102103619&d=1&t=3&u=2885a00d576645a4ae117b521105f543)
263

被折叠的 条评论
为什么被折叠?



