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

article/2025/6/29 8:04:55

第二篇: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.hkcw.cn/article/miovPKncyo.shtml

相关文章

DAY 37 超大力王爱学Python

知识点回顾&#xff1a; 过拟合的判断&#xff1a;测试集和训练集同步打印指标模型的保存和加载 仅保存权重保存权重和模型保存全部信息checkpoint&#xff0c;还包含训练状态 早停策略 作业&#xff1a;对信贷数据集训练后保存权重&#xff0c;加载权重后继续训练50轮&#xf…

ubuntu 添加应用到启动菜单

使用Alacarte菜单编辑器 Alacarte是一个简单易用的菜单编辑器&#xff0c;可以帮助用户添加、删除或编辑应用程序的启动菜单项。 安装Alacarte sudo apt-get install alacarte 执行alacarte alacarte 使用说明 选择新建项目进行添加 "Name"栏填自定义的名称&quo…

3,信号与槽机制

这里绘制好了QT控件,现在需要点击控件,出现相应的响应操作 目录 法一 通过图形界面编写: 1,鼠标选中控件 ,右击,点击转到槽 选择相应的触发操作, 在widget.cpp和widget.h,分别自动增加如下代码: 需要手动添加进程头文件 : 查找QProcess如何使用 保存修改,并…

贪心算法应用:最小反馈顶点集问题详解

贪心算法应用&#xff1a;最小反馈顶点集问题详解 1. 问题定义与背景 1.1 反馈顶点集定义 反馈顶点集(Feedback Vertex Set, FVS)是指在一个有向图中&#xff0c;删除该集合中的所有顶点后&#xff0c;图中将不再存在任何有向环。换句话说&#xff0c;反馈顶点集是破坏图中所…

【Doris基础】Apache Doris中的Version概念解析:深入理解数据版本管理机制

目录 引言 1 Version概念基础 1.1 什么是Version 1.2 Version的核心作用 1.3 Version相关核心概念 2 Version工作机制详解 2.1 Version在数据写入流程中的作用 2.2 Version在数据查询流程中的作用 2.3 Version的存储结构 3 Version的进阶特性 3.1 Version的合并与压…

vLLM实战部署embedding、reranker、senseVoice、qwen2.5、qwen3模型

概述 一个开源的支持高并发的高性能大模型推理引擎。在这篇博客有简单提到过。 学习资料&#xff1a;官方文档&#xff0c;官方中文文档&#xff0c;中文文档。 modelscope 通过vLLM&#xff08;或其他平台、框架&#xff09;部署模型前&#xff0c;需要先下载模型。国内一…

Java函数式编程(中)

三、Stream API &#xff08;1&#xff09;创建操作 构建Arrays.stream(数组)根据数组构建Collection.stream()根据集合构建Stream.of(对象1, 对象2, ...)根据对象构建 生成IntStream.range(a, b)根据范围生成&#xff08;含a 不含b&#xff09;IntStream.rangeClosed(a, b)…

16.FreeRTOS

目录 第1章 FreeRTOS 实时操作系统 1.1 认识实时操作系统 1.1.1 裸机的概念 1.1.2 操作系统的概念 1.2 操作系统的分类 1.3 常见的操作系统 1.4 认识实时操作系统 1.4.1 可剥夺型内核与不可剥夺型内核 1.4.2 嵌入式操作系统的作用 1.4.3 嵌入式操作系统的发展 1.4.4…

windows11安装scoop 20250602

详细的 Scoop 安装步骤&#xff1a; 使用国内镜像安装 Scoop 首先&#xff0c;打开 PowerShell&#xff08;右键点击 win按钮&#xff0c;–>终端&#xff0c;Scoop官方不建议用管理员权限安装)&#xff0c;然后执行以下命令&#xff1a; # 设置 Scoop 安装路径 $env:SCOO…

类和对象(一)

一、面向对象 &#xff08;OOP是面向对象的语言的简称&#xff09; Java是⼀⻔纯⾯向对象的语⾔&#xff0c;在⾯向对象的世界⾥&#xff0c;⼀切皆 为对象。⾯向对象是解决问题的⼀种思想&#xff0c;主要依靠对象之间的交互完成⼀件事情。 面向对象——>不关注过程&…

OpenCV4.4.0下载及初步配置(Win11)

目录 OpenCV4.4.0工具下载安装环境变量系统配置 OpenCV4.4.0 工具 系统&#xff1a;Windows 11 下载 OpenCV全版本百度网盘链接&#xff1a;: https://pan.baidu.com/s/15qTzucC6ela3bErdZ285oA?pwdjxuy 提取码: jxuy找到 opencv-4.0.0-vc14_vc15 下载得到 安装 运行op…

QGIS Python脚本开发(入门级)

随着人工智能技术的飞速发展&#xff0c;编程语言和脚本开发正变得前所未有的便捷。在GIS领域&#xff0c;QGIS作为一款卓越的开源地理信息系统软件&#xff0c;凭借其易于下载、界面简洁、功能强大等诸多优势&#xff0c;赢得了全球用户的青睐。更令人兴奋的是&#xff0c;QGI…

【算法】分支限界

一、基本思想 &#xff08;分支限界&#xff0c; 分枝限界&#xff0c; 分支界限 文献不同说法但都是一样的&#xff09; 分支限界法类似于回溯法&#xff0c;也是一种在问题的解空间树上搜索问题解的算法。 但一般情况下&#xff0c;分支限界法与回溯法的求解目标不同。回溯…

【springcloud】快速搭建一套分布式服务springcloudalibaba(四)

第四篇 基于nacos搭建分布式项目 分布式系统日志&#xff08;skywalkinges&#xff09; 项目所需 maven nacos java8 idea git mysql redis skywalking es 本文主要从客户下单时扣减库存的操作&#xff0c;将链路日志模拟出来&#xff0c;网关系统/用户系统/商品系统/订…

设计模式(行为型)-中介者模式

目录 定义 类图结构展示 角色职责详解 模式的优缺点分析 优点 缺点 适用场景 应用实例 与其他模式的结合与拓展 总结 定义 中介者模式的核心思想可以概括为&#xff1a;用一个中介对象来封装一系列的对象交互。这个中介者就像一个通信枢纽&#xff0c;使各对象不需要…

PMOS以及电源转换电路设计

PMOS的使用 5V_EN5V时&#xff0c;PMOS截止&#xff1b; 5V_EN0V时&#xff0c;PMOS导通&#xff1b; 电源转换电路 当Vout0V时&#xff0c;Vg0V, Vgs>Vth, PMOS导通&#xff0c;只有电池供电&#xff1b; 当Vout5V时&#xff0c;Vg4.9V, Vs4.8V?, Vgs<Vth, PMOS截止&am…

本地部署 DeepSeek R1(最新)【从下载、安装、使用和调用一条龙服务】

文章目录 一、安装 Ollama1.1 下载1.2 安装 二、下载 DeepSeek 模型三、使用 DeepSeek3.1 在命令行环境中使用3.2 在第三方软件中使用 一、安装 Ollama 1.1 下载 官方网址&#xff1a;Ollama 官网下载很慢&#xff0c;甚至出现了下载完显示 无法下载&#xff0c;需要授权 目…

数据治理的演变与AI趋势

知识星球&#xff1a;数据书局。打算通过知识星球将这些年积累的知识分享出来&#xff0c;让各位在数据治理、数据分析的路上少走弯路&#xff0c;另外星球也方便动态更新最近的资料&#xff0c;提供各位一起讨论数据的小圈子 1.数据治理的演变 1.1.摘要 数据治理是指组织管…

Fullstack 面试复习笔记:操作系统 / 网络 / HTTP / 设计模式梳理

Fullstack 面试复习笔记&#xff1a;操作系统 / 网络 / HTTP / 设计模式梳理 面试周期就是要根据JD调整准备内容&#xff08;挠头&#xff09;&#xff0c;最近会混合复习针对全栈这块的内容&#xff0c;目前是根据受伤的JD&#xff0c;优先选择一些基础的操作系统、Java、Nod…

【MIMO稳定裕度】基于数据驱动的多输入多输出系统稳定裕度分析

最近一直在忙着写论文&#xff0c;只能说要写一篇高水平论文确实不容易&#xff0c;要一直反复来回修改调整&#xff0c;要求比较高&#xff0c;所以没太有时间和精力写博客&#xff0c;这两天结束了初稿&#xff0c;又正好是假期&#xff0c;出来冒个泡。 本次分享的主题是&am…