当前位置: 首页 > web >正文

Vue-Leaflet地图组件开发(二)地图核心功能实现

第二篇: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,         // 使用flyTo动画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;}// 构建WFS请求参数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;// 1. 显示加载状态const loading = ElLoading.service({target: '.base-map-container',text: '正在查询地图数据...'});try {// 2. 执行查询const features = await queryFeatures({ type: 'Point', coordinates: [latlng.lng, latlng.lat] },['sde:租赁采集_小区', 'sde:wybld'],{ buffer: 50, limit: 5 });// 3. 处理结果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>
http://www.xdnf.cn/news/10708.html

相关文章:

  • ck-editor5的研究 (6):进一步优化页面刷新时,保存提示的逻辑
  • 5.29 自学测试 Linux基础 Day4
  • webfuture:提示“Strict-Transport-Security头未设置”漏洞的解决方法
  • 深度学习pycharm debug
  • Cesium 自带的标注碰撞检测实现标注避让
  • esp32关于PWM最清晰的解释
  • 渊龙靶场-sql注入(数字型注入)
  • 快乐大冒险:解锁身体里的 “快乐密码”
  • 力扣刷题Day 68:搜索插入位置(35)
  • 如何在 Windows 11 24H2 的任务栏时钟中显示秒数
  • js的时间循环的讲解
  • 100V离线语音通断器
  • java笔记08
  • 15-2021剑侠情缘2-各种修复完善+虚拟机单机端+外网服务端整理+文本教程+视频教程
  • Linux服务器安装GUI界面工具
  • 【数据集】NCAR CESM Global Bias-Corrected CMIP5 Output to Support WRF/MPAS Research
  • Redis部署架构详解:原理、场景与最佳实践
  • Java函数式编程(中)
  • 第十二节:第五部分:集合框架:Set集合的特点、底层原理、哈希表、去重复原理
  • 《QDebug 2025年5月》
  • 基于大模型的急性乳腺炎全病程风险预测与综合治疗方案研究
  • Playwright Python API 测试:从入门到实践
  • 滑动窗口 -- 灵神刷题
  • C# 异常处理进阶:精准获取错误行号的通用方案
  • ubuntu安装devkitPro
  • 什么算得到?什么又算失去?
  • ps曝光度调整
  • 继承(全)
  • 2024年数维杯国际大学生数学建模挑战赛D题城市弹性与可持续发展能力评价解题全过程论文及程序
  • YOLOv10改进|爆改模型|涨点|C2F引入空间和通道注意力模块暴力涨点(附代码+修改教程)