OpenLayers项目实战:用Vue 3 + 天地图WMTS服务,一步步搭建一个可切换图层的地图管理后台

张开发
2026/4/4 2:17:43 15 分钟阅读
OpenLayers项目实战:用Vue 3 + 天地图WMTS服务,一步步搭建一个可切换图层的地图管理后台
Vue 3 OpenLayers实战构建可切换图层的天地图管理后台在当今数据驱动的时代地图应用已成为各类管理后台不可或缺的组成部分。本文将带您从零开始使用Vue 3和OpenLayers构建一个功能完善的地图管理后台重点实现天地图WMTS服务的集成与图层切换功能。不同于简单的API调用教程我们将深入探讨如何将地图功能模块化、工程化打造一个可维护、可扩展的前端地图解决方案。1. 项目环境搭建与基础配置1.1 初始化Vue 3项目首先我们使用Vite创建一个新的Vue 3项目npm create vitelatest ol-vue-map --template vue cd ol-vue-map npm install接下来安装OpenLayers核心依赖npm install ol types/ol1.2 配置基础地图组件创建src/components/BaseMap.vue文件设置地图容器template div refmapContainer classmap-container/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 OSM from ol/source/OSM const mapContainer ref(null) onMounted(() { const map new Map({ target: mapContainer.value, layers: [ new TileLayer({ source: new OSM() }) ], view: new View({ center: [116.4, 39.9], zoom: 10 }) }) }) /script style scoped .map-container { width: 100%; height: 100%; } /style2. 集成天地图WMTS服务2.1 理解天地图WMTS服务天地图提供多种WMTS服务类型包括矢量地图服务(vec_w)影像地图服务(img_w)地形地图服务(ter_w)矢量注记服务(cva_w)影像注记服务(cia_w)每种服务都有对应的WMTS和XYZ两种接入方式我们主要关注WMTS服务。2.2 封装WMTS图层工厂函数在src/utils/mapLayers.js中创建图层工厂import WMTS from ol/source/WMTS import WMTSTileGrid from ol/tilegrid/WMTS import { get as getProjection } from ol/proj import { getTopLeft, getWidth } from ol/extent const TDT_KEY 您的天地图密钥 const projection getProjection(EPSG:4326) const projectionExtent projection.getExtent() const size getWidth(projectionExtent) / 256 const resolutions new Array(18) const matrixIds new Array(18) for (let z 0; z 18; z) { resolutions[z] size / Math.pow(2, z) matrixIds[z] z } export const createTdtWMTSLayer (layerType, visible true) { const layerMap { vector: vec, vector_anno: cva, image: img, image_anno: cia, terrain: ter, terrain_anno: cta } return new TileLayer({ source: new WMTS({ url: http://t0.tianditu.gov.cn/${layerType}_c/wmts, layer: layerMap[layerType], matrixSet: c, format: tiles, projection: projection, tileGrid: new WMTSTileGrid({ origin: getTopLeft(projectionExtent), resolutions: resolutions, matrixIds: matrixIds }), style: default, wrapX: true, crossOrigin: anonymous }), visible, properties: { type: base, source: tianditu, layerType } }) }3. 实现图层管理功能3.1 构建图层状态管理使用Pinia进行图层状态管理创建src/stores/layerStore.jsimport { defineStore } from pinia import { ref, computed } from vue export const useLayerStore defineStore(layers, () { const baseLayers ref([]) const activeBaseLayer ref(null) const overlayLayers ref([]) const setBaseLayers (layers) { baseLayers.value layers if (layers.length 0 !activeBaseLayer.value) { activeBaseLayer.value layers[0] } } const switchBaseLayer (layerId) { baseLayers.value.forEach(layer { layer.setVisible(layer.get(layerId) layerId) }) activeBaseLayer.value baseLayers.value.find(l l.get(layerId) layerId) } return { baseLayers, activeBaseLayer, overlayLayers, setBaseLayers, switchBaseLayer } })3.2 实现图层切换组件创建src/components/LayerSwitcher.vuetemplate div classlayer-switcher div v-forlayer in baseLayers :keylayer.get(layerId) classlayer-item :class{ active: layer.getVisible() } clickswitchLayer(layer.get(layerId)) {{ getLayerName(layer) }} /div /div /template script setup import { computed } from vue import { useLayerStore } from ../stores/layerStore const layerStore useLayerStore() const baseLayers computed(() layerStore.baseLayers) const getLayerName (layer) { const typeMap { vector: 矢量地图, image: 影像地图, terrain: 地形地图 } return typeMap[layer.get(layerType)] || 未知图层 } const switchLayer (layerId) { layerStore.switchBaseLayer(layerId) } /script style scoped .layer-switcher { position: absolute; top: 20px; right: 20px; background: white; padding: 10px; border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.2); z-index: 1000; } .layer-item { padding: 8px 12px; cursor: pointer; border-radius: 3px; margin-bottom: 5px; } .layer-item:hover { background-color: #f5f5f5; } .layer-item.active { background-color: #e6f7ff; color: #1890ff; } /style4. 高级功能扩展4.1 实现图层组管理对于复杂的图层结构我们可以实现图层组管理export class LayerGroupManager { constructor(map) { this.map map this.layerGroups new Map() } addGroup(name, layers []) { const group new LayerGroup({ layers, properties: { groupName: name } }) this.map.addLayer(group) this.layerGroups.set(name, group) return group } getGroup(name) { return this.layerGroups.get(name) } showGroup(name) { const group this.getGroup(name) if (group) { group.setVisible(true) } } hideGroup(name) { const group this.getGroup(name) if (group) { group.setVisible(false) } } }4.2 添加地图交互功能实现基本的标记添加功能export function setupMapInteractions(map) { let drawInteraction null let modifyInteraction null let selectedFeatures new Collection() const vectorLayer new VectorLayer({ source: new VectorSource(), style: new Style({ image: new CircleStyle({ radius: 7, fill: new Fill({ color: red }), stroke: new Stroke({ color: white, width: 2 }) }) }) }) map.addLayer(vectorLayer) const selectInteraction new Select({ layers: [vectorLayer], features: selectedFeatures }) map.addInteraction(selectInteraction) function addDrawInteraction(type) { if (drawInteraction) { map.removeInteraction(drawInteraction) } drawInteraction new Draw({ source: vectorLayer.getSource(), type: type, style: new Style({ fill: new Fill({ color: rgba(255, 255, 255, 0.2) }), stroke: new Stroke({ color: rgba(0, 0, 0, 0.5), width: 2 }), image: new CircleStyle({ radius: 7, fill: new Fill({ color: red }), stroke: new Stroke({ color: white, width: 2 }) }) }) }) drawInteraction.on(drawend, (event) { const feature event.feature feature.setId(uuidv4()) }) map.addInteraction(drawInteraction) } function addModifyInteraction() { if (modifyInteraction) { map.removeInteraction(modifyInteraction) } modifyInteraction new Modify({ features: selectedFeatures, style: new Style({ image: new CircleStyle({ radius: 7, fill: new Fill({ color: rgba(255, 255, 255, 0.2) }), stroke: new Stroke({ color: rgba(0, 0, 255, 0.7), width: 2 }) }) }) }) map.addInteraction(modifyInteraction) } return { addDrawInteraction, addModifyInteraction, vectorLayer } }5. 性能优化与最佳实践5.1 地图渲染性能优化图层预加载策略const layer new TileLayer({ source: new WMTS({ // ...其他配置 transition: 0 // 禁用淡入淡出效果提升性能 }), preload: Infinity // 预加载所有层级瓦片 })使用Web Workers处理大数据量// worker.js self.onmessage function(e) { const features e.data // 在worker中处理复杂的地理数据处理 const processed processFeatures(features) self.postMessage(processed) } // 主线程 const worker new Worker(./worker.js) worker.postMessage(largeFeatureCollection) worker.onmessage function(e) { vectorSource.addFeatures(e.data) }5.2 响应式设计考虑确保地图在不同设备上都能良好显示template div classmap-wrapper div refmapContainer classmap-container/div /div /template script setup import { ref, onMounted, onUnmounted } from vue const mapContainer ref(null) let map null const updateMapSize () { if (map) { setTimeout(() { map.updateSize() }, 100) } } onMounted(() { window.addEventListener(resize, updateMapSize) }) onUnmounted(() { window.removeEventListener(resize, updateMapSize) }) /script style scoped .map-wrapper { position: relative; width: 100%; height: 100%; } .map-container { position: absolute; top: 0; left: 0; right: 0; bottom: 0; } media (max-width: 768px) { .map-container { height: 60vh; } } /style5.3 错误处理与边界情况完善的地图应用需要考虑各种异常情况export async function initMapWithFallback(container, options {}) { try { const map await initPrimaryMap(container, options) return map } catch (primaryError) { console.error(主地图初始化失败:, primaryError) try { const map await initFallbackMap(container, options) return map } catch (fallbackError) { console.error(备用地图初始化失败:, fallbackError) throw new Error(无法初始化任何地图服务) } } } function initPrimaryMap(container, options) { return new Promise((resolve, reject) { try { const map new Map({ target: container, layers: [createTdtWMTSLayer(vector)], view: new View({ center: options.center || [116.4, 39.9], zoom: options.zoom || 10 }) }) resolve(map) } catch (error) { reject(error) } }) }

更多文章