第二篇:Vue-Leaflet地图核心功能实现
1. 地图视窗管理
1.1 视窗状态持久化方案
const saveLocation = async ( options = { } ) => { try { const { saveToLocal = true , saveToServer = false , notify = true } = options; const mapInstance = await map. value?. leafletObject; const bounds = mapInstance. getBounds ( ) ; const viewState = { center : mapInstance. getCenter ( ) , zoom : mapInstance. getZoom ( ) , bounds : { southWest : bounds. getSouthWest ( ) , northEast : bounds. getNorthEast ( ) } , timestamp : new Date ( ) . toISOString ( ) } ; if ( saveToLocal) { localStorage. setItem ( 'mapViewState' , JSON . stringify ( viewState) ) ; } if ( saveToServer) { await api. saveMapView ( { userId : userStore. userId, viewState : JSON . stringify ( viewState) } ) ; } if ( notify) { ElMessage. success ( { message : '地图视窗已保存' , duration : 1500 , showClose : true } ) ; } return viewState; } catch ( error) { console. error ( '保存视窗失败:' , error) ; ElMessage. error ( '保存视窗失败' ) ; throw error; }
} ;
1.2 视窗恢复与边界检查
const restoreView = async ( options = { } ) => { const { fly = true , duration = 1 , maxZoom = 18 } = options; const savedData = localStorage. getItem ( 'mapViewState' ) ; if ( ! savedData) return false ; try { const { center, zoom, bounds } = JSON . parse ( savedData) ; const mapInstance = await map. value?. leafletObject; const isValidView = ( center. lat >= - 90 && center. lat <= 90 && center. lng >= - 180 && center. lng <= 180 && zoom >= mapOptions. value. minZoom && zoom <= mapOptions. value. maxZoom) ; if ( ! isValidView) { throw new Error ( '无效的视窗数据' ) ; } if ( fly) { mapInstance. flyTo ( [ center. lat, center. lng] , zoom, { duration, maxZoom} ) ; } else { mapInstance. setView ( [ center. lat, center. lng] , zoom) ; } return true ; } catch ( error) { console. error ( '恢复视窗失败:' , error) ; return false ; }
} ;
2. 高级要素交互功能
2.1 增强版要素高亮系统
const highlightStyles = { default : { color : '#00ffff' , weight : 5 , opacity : 1 , fillColor : '#00ffff' , fillOpacity : 0.2 } , selected : { color : '#3388ff' , weight : 6 , dashArray : '10, 10' , fillOpacity : 0.3 } , error : { color : '#ff0000' , weight : 7 , fillColor : '#ff9999' }
} ;
const highlightFeature = ( feature, styleType = 'default' ) => { if ( ! feature) return ; const style = highlightStyles[ styleType] || highlightStyles. default; const geojsonLayer = L . geoJSON ( feature, { style } ) ; clearHighlight ( ) ; geojsonLayer. addTo ( mapInstance) ; highlightedLayers. push ( geojsonLayer) ; return geojsonLayer;
} ;
2.2 要素查询与空间分析
const queryFeatures = async ( geometry, layerNames, options = { } ) => { const { buffer = 0 , limit = 10 , sortByDistance = true } = options; let searchArea = geometry; if ( buffer > 0 ) { const buffered = turf. buffer ( geometry, buffer, { units : 'meters' } ) ; searchArea = buffered. geometry; } const wfsParams = { service : 'WFS' , version : '2.0.0' , request : 'GetFeature' , typeNames : layerNames. join ( ',' ) , outputFormat : 'application/json' , srsName : 'EPSG:4490' , bbox : getBboxString ( searchArea) , propertyName : options. fields || '*' , count : limit} ; const results = await axios. get ( ` ${ GEOSERVER_URL } /wfs ` , { params : wfsParams } ) ; let features = results. data. features; if ( sortByDistance && geometry. type === 'Point' ) { const center = turf. point ( [ geometry. coordinates[ 0 ] , geometry. coordinates[ 1 ] ] ) ; features = features. map ( f => ( { ... f, _distance : turf. distance ( center, turf. centerOfMass ( f) ) } ) ) . sort ( ( a, b ) => a. _distance - b. _distance) ; } return features. slice ( 0 , limit) ;
} ;
3. 地图事件高级处理
3.1 防抖点击处理
import { debounce } from 'lodash-es' ; const handleMapClick = debounce ( async ( event ) => { const { latlng } = event; const loading = ElLoading. service ( { target : '.base-map-container' , text : '正在查询地图数据...' } ) ; try { const features = await queryFeatures ( { type : 'Point' , coordinates : [ latlng. lng, latlng. lat] } , [ 'sde:租赁采集_小区' , 'sde:wybld' ] , { buffer : 50 , limit : 5 } ) ; if ( features. length > 0 ) { const primaryFeature = features[ 0 ] ; highlightFeature ( primaryFeature, 'selected' ) ; showFeaturePopup ( primaryFeature, latlng) ; emit ( 'feature-selected' , primaryFeature) ; } } catch ( error) { ElMessage. error ( ` 查询失败: ${ error. message} ` ) ; } finally { loading. close ( ) ; }
} , 300 , { leading : true , trailing : false } ) ;
3.2 自定义地图上下文菜单
const initContextMenu = ( ) => { mapInstance. on ( 'contextmenu' , ( e ) => { L . DomEvent. stop ( e) ; removeContextMenu ( ) ; const menu = L . popup ( { className : 'map-context-menu' } ) . setLatLng ( e. latlng) . setContent ( ` <div class="context-menu"><div class="item" data-action="add-marker"><i class="el-icon-location"></i> 添加标记</div><div class="item" data-action="measure-distance"><i class="el-icon-odometer"></i> 测量距离</div><div class="item" data-action="query-location"><i class="el-icon-search"></i> 查询位置</div></div> ` ) . openOn ( mapInstance) ; contextMenu. value = menu; } ) ;
} ;
const onMenuClick = ( action, latlng ) => { switch ( action) { case 'add-marker' : addCustomMarker ( latlng) ; break ; case 'measure-distance' : startMeasuring ( latlng) ; break ; case 'query-location' : handleMapClick ( { latlng } ) ; break ; }
} ;
4. 地图工具集成
4.1 测量工具实现
const measureControl = { startMeasuring : ( type = 'line' ) => { measuring. value = true ; measureType. value = type; measureLayer = L . featureGroup ( ) . addTo ( mapInstance) ; mapInstance. dragging. disable ( ) ; mapInstance. getContainer ( ) . style. cursor = 'crosshair' ; mapInstance. on ( 'click' , handleMeasureClick) ; mapInstance. on ( 'mousemove' , handleMeasureMove) ; } , endMeasuring : ( ) => { measuring. value = false ; mapInstance. dragging. enable ( ) ; mapInstance. getContainer ( ) . style. cursor = '' ; mapInstance. off ( 'click' , handleMeasureClick) ; mapInstance. off ( 'mousemove' , handleMeasureMove) ; const result = { type : measureType. value, coordinates : currentMeasureCoords. value, length : calculateMeasureLength ( ) , area : calculateMeasureArea ( ) } ; if ( measureLayer) { mapInstance. removeLayer ( measureLayer) ; } return result; }
} ;
4.2 绘图工具集成
import 'leaflet-draw/dist/leaflet.draw.css' ;
import { FeatureGroup, Draw } from 'leaflet-draw' ; const initDrawingTools = ( ) => { const drawnItems = new FeatureGroup ( ) ; mapInstance. addLayer ( drawnItems) ; const drawControl = new L. Control. Draw ( { position : 'topright' , draw : { polygon : { allowIntersection : false , showArea : true , metric : true } , circle : false , rectangle : false , marker : { icon : new L. Icon. Default ( { iconUrl : '/custom-marker.png' } ) } } , edit : { featureGroup : drawnItems} } ) ; mapInstance. addControl ( drawControl) ; mapInstance. on ( L . Draw. Event. CREATED , ( e ) => { const layer = e. layer; drawnItems. addLayer ( layer) ; emit ( 'feature-created' , { type : e. layerType, geojson : layer. toGeoJSON ( ) , layer : layer} ) ; } ) ;
} ;
5. 性能优化技巧
5.1 图层加载优化
const lazyLoadLayer = ( layerName ) => { if ( ! loadedLayers. value. includes ( layerName) ) { const loading = ElLoading. service ( { text : ` 加载 ${ layerName} 图层... ` } ) ; getWMSLayer ( layerName) . addTo ( mapInstance) . once ( 'load' , ( ) => { loadedLayers. value. push ( layerName) ; loading. close ( ) ; } ) . on ( 'error' , ( ) => { loading. close ( ) ; ElMessage. error ( ` ${ layerName} 图层加载失败 ` ) ; } ) ; }
} ;
5.2 视窗变化时的优化策略
const onViewChange = debounce ( async ( e ) => { const zoomLevel = mapInstance. getZoom ( ) ; const bounds = mapInstance. getBounds ( ) ; if ( zoomLevel < 10 ) { switchToLowDetailLayers ( ) ; } else { switchToHighDetailLayers ( ) ; } preloadDataForBounds ( bounds) ;
} , 500 ) ;
6. 完整功能集成示例
<template><div class="advanced-map-container"><l-map ref="map" :options="mapOptions" @ready="initAdvancedMap"><!-- 基础图层 --><l-tile-layer :url="baseLayerUrl" /><!-- 绘图工具栏 --><l-control position="topright" v-if="drawingEnabled"><button @click="toggleDrawing('polygon')">绘制多边形</button><button @click="toggleDrawing('marker')">添加标记</button></l-control><!-- 测量结果展示 --><l-control position="bottomleft" class="measure-result">距离: {{ measureResult.distance }} m | 面积: {{ measureResult.area }} m²</l-control></l-map><!-- 地图控制面板 --><div class="map-control-panel"><el-button-group><el-button @click="saveCurrentView">保存视窗</el-button><el-button @click="restoreView">恢复视窗</el-button><el-button @click="startMeasurement('line')">测量距离</el-button><el-button @click="startMeasurement('polygon')">测量面积</el-button></el-button-group></div></div>
</template><script setup>
// 这里集成前面介绍的所有高级功能
</script><style>
.advanced-map-container {position: relative;height: 100%;
}.map-control-panel {position: absolute;top: 10px;left: 10px;z-index: 1000;background: white;padding: 10px;border-radius: 4px;box-shadow: 0 2px 12px rgba(0,0,0,0.1);
}.measure-result {background: white;padding: 5px 10px;border-radius: 4px;font-size: 14px;
}
</style>