Vue3项目里用天地图API做个地图功能,从配置到覆盖物完整流程
Vue3实战从零构建天地图可视化组件全流程指南在数据驱动的现代Web应用中地图功能已成为管理后台和数据大屏的核心组件。作为国内领先的地理信息服务天地图凭借其稳定的服务和丰富的地图数据成为众多企业的首选。本文将带您从零开始在Vue3项目中完整实现一个具备标记点、信息窗口等高级功能的天地图组件并分享TypeScript深度集成和性能优化的实战经验。1. 环境准备与基础配置天地图服务的接入始于开发者账号的申请。访问天地图开放平台完成个人或企业认证后进入控制台→应用管理创建新应用。这里需要特别注意服务类型选择浏览器端JS API白名单配置建议设置为*允许所有域名正式环境请替换为具体域名配额管理免费版每日调用限额为10万次超出需购买商用套餐获取到API Key后我们有两种引入方式可选!-- 方式一传统script标签引入 -- script srchttp://api.tianditu.gov.cn/api?v4.0tk您的密钥/script !-- 方式二动态加载推荐 -- script function loadTianDiTuAPI() { return new Promise((resolve) { if (window.T) return resolve(true) const script document.createElement(script) script.src http://api.tianditu.gov.cn/api?v4.0tk您的密钥 script.onload () resolve(true) document.head.appendChild(script) }) } /script对于Vue3项目推荐采用动态加载方案它具备以下优势避免阻塞主线程提升页面加载性能实现按需加载减少初始包体积便于错误处理和重试机制实现2. 核心地图组件的构建2.1 组件基础结构使用Vue3的Composition API创建基础组件框架template div classtmap-container div refmapEl classtmap-viewport/div slot/slot /div /template script langts setup import { ref, onMounted, onBeforeUnmount, watch } from vue const props defineProps({ center: { type: Array as () [number, number], default: () [116.404, 39.915] }, zoom: { type: Number, default: 12 }, mapType: { type: String, default: normal } // normal/terrain/satellite }) const mapEl refHTMLElement | null(null) let mapInstance: any null onMounted(async () { await initMap() }) onBeforeUnmount(() { destroyMap() }) /script style scoped .tmap-container { position: relative; width: 100%; height: 100%; } .tmap-viewport { width: 100%; height: 100%; background: #f0f2f5; } /style2.2 地图初始化与响应式控制天地图API的初始化需要特别注意坐标系的选择。国内常用的坐标系包括坐标系类型标识符适用场景经纬度坐标EPSG:4326国际标准兼容GPS设备墨卡托投影EPSG:3857网络地图通用显示更精确// 在setup函数中继续编写 const initMap async () { await loadTianDiTuAPI() if (!mapEl.value) return const T window.T mapInstance new T.Map(mapEl.value, { projection: EPSG:3857 // 推荐使用墨卡托投影 }) // 设置初始视图 mapInstance.centerAndZoom( new T.LngLat(...props.center), props.zoom ) // 设置地图类型 const typeMap { normal: TMAP_NORMAL_MAP, terrain: TMAP_TERRAIN_MAP, satellite: TMAP_HYBRID_MAP } mapInstance.setMapType(typeMap[props.mapType]) // 添加基本控件 mapInstance.addControl(new T.Control.Zoom()) mapInstance.addControl(new T.Control.OverviewMap()) } // 响应式更新处理 watch(() props.center, (newVal) { if (mapInstance newVal) { mapInstance.setCenter(new T.LngLat(...newVal)) } }) watch(() props.zoom, (newVal) { if (mapInstance newVal) { mapInstance.setZoom(newVal) } })3. 高级覆盖物实现技巧3.1 标记点与信息窗口天地图提供多种标记点样式我们还可以自定义图标const addMarker (lnglat: [number, number], options {}) { if (!mapInstance) return null const T window.T const defaultIcon new T.Icon({ iconUrl: //api.tianditu.gov.cn/img/markers/marker_red.png, iconSize: new T.Point(25, 35), iconAnchor: new T.Point(12, 35) }) const marker new T.Marker( new T.LngLat(...lnglat), { icon: options.icon || defaultIcon } ) mapInstance.addOverLay(marker) // 信息窗口实现 if (options.content) { const infoWindow new T.InfoWindow({ content: options.content, position: new T.LngLat(...lnglat) }) marker.addEventListener(click, () { infoWindow.open(mapInstance) }) } return marker }3.2 折线与多边形绘制对于路径规划或区域标注需求可以使用以下方法const addPolyline (points: Array[number, number], style {}) { if (!mapInstance || points.length 2) return null const T window.T const line new T.Polyline( points.map(p new T.LngLat(...p)), { color: #3388ff, weight: 3, opacity: 0.8, ...style } ) mapInstance.addOverLay(line) return line } const addPolygon (points: Array[number, number], style {}) { if (!mapInstance || points.length 3) return null const T window.T const polygon new T.Polygon( points.map(p new T.LngLat(...p)), { color: #3388ff, weight: 2, opacity: 0.6, fillColor: #3388ff, fillOpacity: 0.3, ...style } ) mapInstance.addOverLay(polygon) return polygon }4. 性能优化与最佳实践4.1 大数据量渲染优化当地图需要展示大量标记点时如门店分布、设备点位直接渲染会导致性能急剧下降。可采用以下策略聚合显示使用标记点聚类插件const initCluster (points: Array[number, number, any]) { const T window.T const markers points.map(p { const marker new T.Marker(new T.LngLat(p[0], p[1])) marker.data p[2] // 附加数据 return marker }) const cluster new T.MarkerClusterer(mapInstance, { gridSize: 80, maxZoom: 15, styles: [{ url: //api.tianditu.gov.cn/img/markers/marker_blue.png, size: new T.Point(30, 40), textColor: #fff, textSize: 12 }] }) cluster.addMarkers(markers) return cluster }视图区域筛选只渲染当前视野范围内的标记点const setupViewportFilter () { if (!mapInstance) return const updateVisibleMarkers () { const bounds mapInstance.getBounds() allMarkers.forEach(marker { const visible bounds.contains(marker.getPosition()) marker[visible ? show : hide]() }) } mapInstance.addEventListener(moveend, updateVisibleMarkers) mapInstance.addEventListener(zoomend, updateVisibleMarkers) }4.2 内存管理与事件清理Vue组件卸载时务必清理地图资源和事件监听避免内存泄漏const destroyMap () { if (!mapInstance) return // 移除所有覆盖物 mapInstance.clearOverLays() // 移除所有事件监听 mapInstance.removeEventListener(click) mapInstance.removeEventListener(moveend) // 销毁地图实例 mapInstance.destroy() mapInstance null }5. 组件封装与业务集成5.1 可复用组件设计将地图功能封装为业务组件时建议采用以下设计模式template TianDiTuMap :centermapCenter :zoomzoomLevel marker-clickhandleMarkerClick template #controls MapToolbar searchhandleSearch / /template /TianDiTuMap /template script setup // 业务逻辑处理 const handleMarkerClick (marker) { fetchDetailData(marker.id).then(data { showInfoWindow(data) }) } /script5.2 TypeScript深度集成为天地图API创建类型声明文件tianditu.d.tsdeclare namespace TMap { class Map { constructor(container: HTMLElement, options?: MapOptions) setCenter(lnglat: LngLat): void setZoom(zoom: number): void addOverLay(overlay: Overlay): void // 其他方法声明... } interface LngLat { lng: number lat: number } // 其他类型声明... } declare const T: typeof TMap在组件中使用时获得完整的类型提示let mapInstance: TMap.Map | null null const initMap () { mapInstance new T.Map(mapEl.value!, { projection: EPSG:3857 }) // 现在可以获得完整的类型检查 }