Vue3 + Leaflet 实战:搞定天地图与CGCS2000 WMTS服务叠加(附完整避坑代码)

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]);
};

常见问题排查

  1. 图层不显示:检查密钥是否有效,URL中的 c 是否与坐标系匹配
  2. 偏移问题:确认使用的是 img_c 而非 img_w 服务
  3. 跨域问题:确保服务器配置了正确的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 性能优化技巧

  1. 图层加载策略优化

    • 使用 L.LayerGroup 管理同类图层
    • 实现按需加载(视口内加载)
    • 对静态数据使用 L.GeoJSON 而非WMTS
  2. 内存管理

    onUnmounted(() => {
      if (map.value) {
        map.value.eachLayer(layer => {
          map.value.removeLayer(layer);
        });
        map.value.remove();
      }
    });
    
  3. 事件处理优化

    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 常见问题调试指南

  1. 图层不显示问题排查流程

    • 检查浏览器开发者工具中的网络请求
    • 确认WMTS服务URL是否正确
    • 验证坐标系定义是否与服务匹配
    • 检查天地图密钥是否有效
  2. 跨域问题解决方案

    // 在vue.config.js中添加代理配置
    module.exports = {
      devServer: {
        proxy: {
          '/geoserver': {
            target: 'http://your-geoserver-domain',
            changeOrigin: true
          }
        }
      }
    };
    
  3. 性能问题分析工具

    • 使用Leaflet的 L.Util.stamp 跟踪图层实例
    • Chrome开发者工具的Performance面板记录性能
    • 监控内存使用情况,防止泄漏
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值