Vue3项目实战:用BMapGL+BMapGLLib实现地图标注与绘制(附完整代码)
Vue3项目实战用BMapGLBMapGLLib实现地图标注与绘制附完整代码在Vue3生态中集成地图功能已成为企业级应用的常见需求。百度地图GL版BMapGL凭借其WebGL渲染引擎为现代前端项目提供了更流畅的地图交互体验。本文将深入探讨如何在Vue3的Composition API环境下高效整合BMapGL核心库与BMapGLLib扩展工具实现从基础地图展示到复杂图形绘制的全流程解决方案。1. 环境配置与SDK接入1.1 密钥申请与类型声明首先访问百度地图开放平台申请WebGL版密钥AK建议为开发和生产环境配置不同的密钥。在Vue3项目中创建src/types/bmapgl.d.ts文件声明类型declare interface Window { BMapGL: any; BMapGLLib: any; } type MapPoint { lng: number; lat: number; }; type DrawingMode marker | polyline | polygon | rectangle | circle;1.2 动态脚本加载方案不同于传统script标签引入方式我们采用动态加载方案以优化首屏性能// utils/loadScript.ts export const loadBMapGL (ak: string) { return new Promise((resolve) { if (window.BMapGL) return resolve(true); const script document.createElement(script); script.src //api.map.baidu.com/api?typewebglv1.0ak${ak}; script.onload () { loadDrawingManager().then(resolve); }; document.head.appendChild(script); }); }; const loadDrawingManager () { return new Promise((resolve) { if (window.BMapGLLib) return resolve(true); const link document.createElement(link); link.href //mapopen.cdn.bcebos.com/github/BMapGLLib/DrawingManager/src/DrawingManager.min.css; link.rel stylesheet; const script document.createElement(script); script.src //mapopen.cdn.bcebos.com/github/BMapGLLib/DrawingManager/src/DrawingManager.min.js; document.head.append(link, script); script.onload resolve; }); };提示动态加载需考虑网络异常情况建议添加错误处理逻辑和加载超时机制2. Vue3地图组件封装2.1 基础地图组件实现创建BMapGLContainer.vue组件采用Composition API组织逻辑template div refmapContainer classbmap-gl-container :style{ height: ${height}px } /div /template script setup langts import { onMounted, ref, watch } from vue; const props defineProps({ height: { type: Number, default: 500 }, center: { type: Object as () MapPoint, required: true }, zoom: { type: Number, default: 12 } }); const mapContainer refHTMLElement(); const mapInstance refany(); const initMap async () { await loadBMapGL(your_ak); mapInstance.value new window.BMapGL.Map(mapContainer.value, { enableMapClick: false, displayOptions: { building: true // 开启3D建筑物显示 } }); const point new window.BMapGL.Point(props.center.lng, props.center.lat); mapInstance.value.centerAndZoom(point, props.zoom); mapInstance.value.enableScrollWheelZoom(true); }; onMounted(initMap); /script style scoped .bmap-gl-container { position: relative; border: 1px solid #eee; border-radius: 4px; overflow: hidden; } /style2.2 响应式地图控制利用Vue3的响应式特性实现地图状态同步// 在setup()中添加 watch(() props.center, (newVal) { if (!mapInstance.value) return; const point new window.BMapGL.Point(newVal.lng, newVal.lat); mapInstance.value.panTo(point); }, { deep: true }); watch(() props.zoom, (newVal) { mapInstance.value?.setZoom(newVal); });3. 高级绘制功能实现3.1 可组合式绘图逻辑创建useMapDrawing组合式函数封装绘图相关逻辑// composables/useMapDrawing.ts import { ref } from vue; export default function useMapDrawing(mapInstance: any) { const activeMode refDrawingMode(); const drawingManager refany(); const overlays refany[]([]); const initDrawingManager (options {}) { drawingManager.value new window.BMapGLLib.DrawingManager( mapInstance.value, { enableCalculate: true, enableSorption: true, sorptiondistance: 15, ...options } ); }; const setDrawingMode (mode: DrawingMode) { activeMode.value mode; drawingManager.value?.setDrawingMode(mode); drawingManager.value?.open(); }; const clearOverlays () { overlays.value.forEach(overlay { mapInstance.value.removeOverlay(overlay); }); overlays.value []; }; return { activeMode, drawingManager, overlays, initDrawingManager, setDrawingMode, clearOverlays }; }3.2 绘图事件与Vue状态联动扩展useMapDrawing函数添加事件监听逻辑// 在useMapDrawing.ts中继续添加 const setupEventListeners () { drawingManager.value?.addEventListener(overlaycomplete, (event: any) { const overlay event.overlay; overlays.value.push(overlay); switch (event.drawingMode) { case marker: handleMarkerComplete(overlay); break; case circle: handleCircleComplete(overlay); break; // 其他图形处理... } }); }; const handleMarkerComplete (marker: any) { const position marker.getPosition(); console.log(Marker placed at:, position.lng, position.lat); // 反向地理编码示例 const geocoder new window.BMapGL.Geocoder(); geocoder.getLocation(position, (result: any) { console.log(Address:, result.address); }); }; const handleCircleComplete (circle: any) { const center circle.getCenter(); const radius circle.getRadius(); console.log(Circle - Center: (${center.lng},${center.lat}), Radius: ${radius}m); };4. 性能优化与最佳实践4.1 内存管理策略百度地图实例会持续占用内存需在组件卸载时正确销毁// 在BMapGLContainer.vue的setup()中添加 onUnmounted(() { if (mapInstance.value) { mapInstance.value.destroy(); mapInstance.value null; } });4.2 绘图数据序列化实现图形数据的保存与恢复功能// 扩展useMapDrawing.ts const serializeOverlays () { return overlays.value.map(overlay { if (overlay instanceof window.BMapGL.Marker) { const position overlay.getPosition(); return { type: marker, lng: position.lng, lat: position.lat }; } // 其他图形类型处理... }); }; const deserializeOverlays (data: any[]) { clearOverlays(); data.forEach(item { let overlay: any; const point new window.BMapGL.Point(item.lng, item.lat); switch (item.type) { case marker: overlay new window.BMapGL.Marker(point); break; // 其他图形类型处理... } if (overlay) { mapInstance.value.addOverlay(overlay); overlays.value.push(overlay); } }); };4.3 自定义绘图样式通过样式配置提升绘图视觉效果const drawingStyles { strokeColor: #1890ff, fillColor: #1890ff40, strokeWeight: 2, strokeOpacity: 0.8, fillOpacity: 0.3, strokeStyle: solid // dashed | solid }; // 使用时 initDrawingManager({ circleOptions: drawingStyles, polygonOptions: drawingStyles, rectangleOptions: drawingStyles });5. 典型业务场景实现5.1 区域标注系统结合Element Plus实现完整的区域标注流程template div classmap-editor el-button-group el-button v-formode in modes :keymode :typeactiveMode mode ? primary : clicksetMode(mode) {{ modeLabels[mode] }} /el-button /el-button-group b-map-gl-container refmap :centerinitialCenter :zoom14 readyonMapReady / el-button clicksaveAreas保存标注/el-button /div /template script setup import { ref } from vue; import useMapDrawing from /composables/useMapDrawing; const initialCenter { lng: 116.404, lat: 39.915 }; const map ref(); const { activeMode, setDrawingMode, serializeOverlays } useMapDrawing(); const modes [marker, circle, rectangle]; const modeLabels { marker: 点标注, circle: 圆形区域, rectangle: 矩形区域 }; const onMapReady (mapInstance) { drawing.initDrawingManager(mapInstance); }; const setMode (mode) { setDrawingMode(mode); }; const saveAreas () { const areaData serializeOverlays(); console.log(保存区域数据:, areaData); // 提交到后端... }; /script5.2 坐标转换处理处理不同坐标系之间的转换问题// utils/coordTransform.ts export const wgs84ToBd09 (lng: number, lat: number) { // 实际项目中应使用百度官方坐标转换API // 这里简化处理生产环境请勿直接使用 const x lng * 1.0002; const y lat * 1.0001; return { lng: x, lat: y }; }; // 在组件中使用 const point wgs84ToBd09(116.404, 39.915); mapInstance.value.panTo(new window.BMapGL.Point(point.lng, point.lat));6. 调试技巧与常见问题6.1 开发环境问题排查常见问题及解决方案地图不显示检查AK是否正确、网络请求是否成功、容器尺寸是否有效绘图工具不生效确认BMapGLLib加载顺序应在BMapGL之后加载事件不触发检查地图实例是否已初始化完成6.2 性能监控方案添加地图性能日志mapInstance.value.addEventListener(tilesloaded, () { console.timeEnd(mapTilesLoad); }); console.time(mapTilesLoad);在项目实际开发中地图组件的复用边界需要仔细设计。将基础地图功能与业务逻辑分离通过组合式函数封装通用能力可以使地图模块保持灵活性和可维护性。对于复杂交互场景建议采用状态管理工具如Pinia来管理地图状态避免组件间直接耦合。