Vue3组件+leaflet,实现重叠marker的Popup切换显示
一、前言
GIS开发过程中,经常需要绘制marker,这些marker很大概率会有坐标相同导致的叠加问题,这种情况下会降低使用体验感。所以我们可以将叠加的marker的popup做一个分页效果,可以切换显示的marker。
二、技术要点
我们以leaflet为例,我们可以使用leaflet的popup展示marker的详细信息,简单的信息展示我们可以直接拼接html字符串就能解决,但是我们需要切换信息,这样就会涉及到dom的事件监听还有信息内容的动态更新,如果用js+html这样的方式实现起来非常复杂,于是我们看看能不能直接用vue3的组件作为popup的content,bindPopup(<String|HTMLElement|Function|Popup>content,<Popup options>options?) 这个是marker绑定popup的函数,可以看到可以传HTMLElement、String也可以是一个Function,于是可以写一个Function返回Vue3的组件$el。
三、步骤
1、创建Popup的内容Vue3组件
/**
* WarningSignalPopup.vue 地图marker的popup内容组件
* @Author ZhangJun
* @Date 2025/3/14 9:37
**/
<template><div class="w-[300px]"><div><div class="font-bold text-lg mb-2">{{ currentMarkerContent?.headline }}</div><div class="flex items-start gap-2 mb-2"><img class="h-[65px] w-min-[60px]" :src="currentMarkerContent?.iconUrl" :alt="levelNames[currentMarkerContent?.severity]" /><div><div>预警等级:{{ levelNames[currentMarkerContent?.severity] }}</div><div>所属区域:{{ currentMarkerContent?.regionName }}</div><div>发布时间:{{ currentMarkerContent?.sendtime }}</div><div>发布单位:{{ currentMarkerContent?.sender }}</div></div></div><div class="w-full">{{ currentMarkerContent?.description }}</div></div><el-space size="8px" class="mt-4" v-show="popupContentList.length > 1"><el-button type="primary" size="small" @click="handleClickPrev" :icon="ArrowLeft"></el-button><div>{{ markerIndex + 1 }} / {{ popupContentList.length }}</div><el-button type="primary" size="small" @click="handleClickNext" :icon="ArrowRight"></el-button></el-space></div>
</template><script setup>
import { ref, defineExpose, watchEffect, onUnmounted } from 'vue'
import { ArrowLeft, ArrowRight } from '@element-plus/icons-vue'
//marker的popup内容列表
let popupContentList = ref([])
//marker对象
let targetMarker = ref(null)
//当前marker的索引
let markerIndex = ref(0)const levelNames = {Blue: '蓝色',Yellow: '黄色',Orange: '橙色',Red: '红色',
}//当前需要显示的marker内容
let currentMarkerContent = ref({})/*** 上一个*/
const handleClickPrev = () => {if (markerIndex.value > 0) {markerIndex.value = (markerIndex.value - 1) % popupContentList.value.length}
}/*** 下一个*/
const handleClickNext = () => {if (markerIndex.value < popupContentList.value.length - 1) {markerIndex.value = (markerIndex.value + 1) % popupContentList.value.length}
}/*** 重置*/
const reset = () => {popupContentList.value = []targetMarker.value = nullmarkerIndex.value = 0currentMarkerContent.value = {}
}//这里就是将marker的icon更新,跟popup显示的image一致
watchEffect(() => {//marker不能为nullif (targetMarker.value) {//获取marker要显示的popup内容currentMarkerContent.value = popupContentList.value?.[markerIndex.value]//获取popup内容的标记图片url,这里跟marker的iconUrl是一样的地址let iconUrl = currentMarkerContent.value?.iconUrl//得到marker的icon对象let icon = targetMarker.value?.getIcon()//判断iconUrl是否与marker的iconUrl不同,如果不同,则更新marker的iconUrlif (iconUrl && iconUrl !== icon.options.iconUrl) {icon.options.iconUrl = iconUrltargetMarker.value.setIcon(icon)}}
})defineExpose({targetMarker,popupContentList,markerIndex,reset,
})
</script><style scoped lang="scss"></style>
2、用于生成marker的hook
/*** @ClassName UseWarningSignal.js* @Description 预警信号展示hook* @Author ZhangJun* @Date 2025/3/12 17:36**/
import { ref } from 'vue'
import { getAction } from '@/utils/manage'
import moment from 'moment'/*** 预警信号hook* @param warningSignalPopupRef 预警信号popup弹窗实例* @returns {{getWarningSignalList: getWarningSignalList, warningSignalList: Ref<UnwrapRef<[]>, UnwrapRef<[]> | []>}}*/
export function useWarningSignal(warningSignalPopupRef) {//预警信号数据列表let warningSignalList = ref([])let warningIconLayer = null/*** 获取预警信号列表* @param timeKey 时间标识*/const getWarningSignalList = (timeKey = moment().format('YYYY-MM-DD')) => {//todo: 这里应该是从后端获取数据,这里只是模拟数据let endTime = moment('2025-01-05')let startTime = endTime.clone().subtract(2, 'days').format('YYYY-MM-DD')getAction('productInfo/getProductInfoTxt', { startTime, endTime: endTime.format('YYYY-MM-DD') }).then(res => {res[1].severity = 'Red'warningSignalList.value = resdrawWarningSignalMarkers(res)})}/*** 绘制预警信号标记* @param warningSignalList*/const drawWarningSignalMarkers = (warningSignalList = warningSignalList.value) => {//因为返回的数据里面没有灾害名称,所以只能这种操作let districtTypeList = ['冰雹', '台风', '大雾', '大风', '寒潮', '山洪', '干旱', '暴雨', '暴雪', '森林火险', '道路结冰', '雷电', '雷雨大风', '霜冻', '高温']let warningLevelList = ['', 'Blue', 'Yellow', 'Orange', 'Red']//先清除之前的标记if (warningIconLayer) {warningIconLayer.clearLayers()}//没有行政区数据,所以只能通过行政区名称来获取wizMap.getJson('/json/area.json', data => {let { features } = data || {}let allRegionData = {}features?.forEach(({properties: {name,center: [lon, lat],},}) => {name = name.replace('省', '').replace('市', '').replace('区', '').replace('县', '')allRegionData[name] = { lon, lat }},)let markersDict = {}warningSignalList.forEach(item => {let { headline, description, sender, sendtime, severity } = itemlet distressType = districtTypeList.find(name => headline?.includes(name))let level = warningLevelList.findIndex(val => val === severity)let regionName = ''if (sender.includes('县')) {regionName = sender.split('县')?.[0]} else if (sender.includes('区')) {regionName = sender.split('区')?.[0]} else if (sender.includes('市')) {regionName = sender.split('市')?.[0]} else if (sender.includes('省')) {regionName = sender.split('省')?.[0]}//由于没有行政区的数据,所以只能通过行政区名称来判断if (regionName) {let iconUrl = `/icons/warningIcon/${distressType}/${level}.png`let myIcon = L.icon({iconUrl: iconUrl,iconSize: [44 * 1.5, 36 * 1.5],iconAnchor: [22 * 1.5, 18 * 1.5],})//坐标let { lat, lon } = allRegionData[regionName]let marker = L.marker([lat, lon], { icon: myIcon })let markerIndex = markersDict?.[regionName]?.length - 1//提示框内容,保存到markersDict中marker.attributes = {...item,iconUrl,}//添加到图标集合中if (!markersDict?.[regionName]) {markersDict[regionName] = [marker]} else {markersDict[regionName]?.push(marker)}}})let markers = Object.entries(markersDict).map(([key, markerObjects]) => {let [markerObject] = markerObjectsmarkerObject.bindPopup(e => {//先重置popup内容warningSignalPopupRef.value.reset()//设置图标对象warningSignalPopupRef.value.targetMarker = markerObject//设置弹窗内容列表warningSignalPopupRef.value.popupContentList = markerObjects.map(({ attributes }) => attributes)//返回弹窗组件实例,这里是vue组件实例,所以需要用$el来获取domreturn warningSignalPopupRef.value.$el},{ maxWidth: 300 },).on('popupclose', () => {//关闭弹窗时,清除图标对象warningSignalPopupRef.value.markerIndex = 0})return markerObject})if (markers.length) {warningIconLayer = L.layerGroup(markers).addTo(window.wizMap.map)}})}getWarningSignalList()return {warningSignalList,getWarningSignalList,}
}
代码还有可以优化的地方,受数据条件的影响,获取marker坐标时只能通过json文件查询后填充。
四、效果


相关文章:
Vue3组件+leaflet,实现重叠marker的Popup切换显示
一、前言 GIS开发过程中,经常需要绘制marker,这些marker很大概率会有坐标相同导致的叠加问题,这种情况下会降低使用体验感。所以我们可以将叠加的marker的popup做一个分页效果,可以切换显示的marker。 二、技术要点 我们以leaf…...
将COCO格式的物体检测数据集划分训练集、验证集和测试集
目录 导入所需库 定义数据集路径 创建输出目录 读取JSON注释文件 随机打乱图像列表 计算划分大小 复制图像到相应文件夹 完整代码 导入所需库 我们需要以下Python库: os:处理文件路径。 json:读取和写入JSON文件。 numpyÿ…...
机器学习之距离度量方法
常见的距离度量方法及相关函数、图示如下: 1. 欧几里得距离(Euclidean Distance) 函数公式:对于两个 ( n ) 维向量 ( x = ( x 1 , x 2 , ⋯ ,...
3.1 在VisionPro脚本中添加CogGraphicLabel
本案例需要实现如下功能: 1.加载toolBlock 2.加载图片, 3.运行Block 4.VisionPro中添加脚本显示数值。 见下图:详细代码(C#以及visionPro)见下面链接: https://download.csdn.net/download/qq_340474…...
自动化APP测试APPium的元素等待
在使用Appium进行移动应用自动化测试时,有三种等待。 隐式等待driver.implicitly_wait() 显式等待(常用) time.sleep() 隐式等待(Implicit Wait) 应用场景: 当你希望对所有元素定位操作设置统一的超时…...
AI:Machine Learning Data Science
机器学习与数据科学 左侧 机器学习 Machine Learning 机器学习是一门多领域交叉学科,涉及概率论、统计学、逼近论、凸分析、算法复杂度理论等多门学科。专门研究计算机怎样模拟或实现人类的学习行为,以获取新的知识或技能,重新组织已有的知…...
软件需求分类、需求获取(高软46)
系列文章目录 软件需求分类,需求获取 文章目录 系列文章目录前言一、软件需求二、获取需求三、真题总结 前言 本节讲明软件需求分类、需求获取的相关知识。 一、软件需求 二、获取需求 三、真题 总结 就是高软笔记,大佬请略过!...
vue3vue-elementPlus-admin框架中form组件的upload写法
dialog中write组件代码 let ImageList reactive<UploadFile[]>([])const formSchema reactive<FormSchema[]>([{field: ImageFiles,label: 现场图片,component: Upload,colProps: { span: 24 },componentProps: {limit: 5,action: PATH_URL /upload,headers: {…...
嵌入式Linux | 什么是 BootLoader、Linux 内核(kernel)、和文件系统?
01 什么是 BootLoader 呢? 它是个引导程序,也就是硬件复位以后第一个要执行的程序,它主要工作就是初始化操作系统运行的环境,比如说内存、定时器、缓冲器等,当这个工作做完以后,再把操作系统的代码加载…...
【ARM中R0寄存器】
ARM中R0寄存器 1 RO介绍1.1 R0 的主要作用1 函数返回值2 函数参数3 通用寄存器4 与其他寄存器的区别 1.2 示例 1 RO介绍 在ARM架构中,R0寄存器是一个通用寄存器;是16 个通用寄存器(R0 到 R15)中的第一个,通常用于存储…...
【JDK17】Jlink一秒生成精简的JRE
之前介绍了 Java17模块化的JDK,模块化后按需使用Jlink 用于精简生成 JRE 环境,这让快速的开发环境增强了编码的愉悦感。在实际生产环境中使用 mave 进行项目的构建,其次再是精简 JRE 缩小容器镜像体积,为实现一体化的流程…...
【第9章】亿级电商平台订单系统-整体技术架构设计
1-1 本章导学 课程主题:系统蓝图描绘与整体技术架构设计核心学习内容: ▶️ 订单系统的整体技术架构设计 ▶️ 架构设计核心方法论与实践应用本章核心内容架构 1. 技术预研 架构设计的基础支撑环节关键技术可行性分析与选型依据2. 整体技术架构设计方法与步骤 结构化设计方法…...
函数(函数的概念、库函数、自定义函数、形参和实参、return语句、数组做函数参数、嵌套调用和链式访问、函数的声明和定义、static和extern)
一、函数的概念 •C语⾔中的函数:⼀个完成某项特定的任务的⼀⼩段代码 •函数又被翻译为子函数(更准确) •在C语⾔中我们⼀般会⻅到两类函数:库函数 ⾃定义函数 二、库函数 1 .标准库和头文件 •C语⾔的国际标准ANSIC规定了⼀…...
AGI大模型(6):提示词模型进阶
1 零样本提示 如今,经过⼤量数据训练并调整指令的LLM能够执⾏零样本任务。 代码如下: from openai import OpenAI from dotenv import load_dotenv load_dotenv() # 初始化 OpenAI 服务。 client = OpenAI()prompt = """ 将⽂本分类为中性、负⾯或正⾯。 ⽂…...
ImGui 学习笔记(五) —— 字体文件加载问题
ImGui 加载字体文件的函数似乎存在编码问题,这一点可能跟源文件的编码也有关系,我目前源文件编码是 UTF-16。 当参数中包含中文字符时,ImGui 内部将字符转换为宽字符字符集时候,采用的 MultiByteToWideChar API 参数不太对&#…...
OpenCV计算摄影学(20)非真实感渲染之增强图像的细节函数detailEnhance()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 此滤波器增强特定图像的细节。 cv::detailEnhance用于增强图像的细节,通过结合空间域和频率域的处理,提升图像中特定细节…...
Android PC 要来了?Android 16 Beta3 出现 Enable desktop experience features 选项
在之前的 《Android 桌面窗口新功能推进》 我们就聊过,Google 就一直在努力改进 Android 的内置桌面模式,例如添加了适当的窗口标题、捕捉窗口的能力、悬停选项、窗口大小调整、最小化支持、app-to-web 等。 比如在搭载 Android 15 QPR 1 Beta 2 的 Pix…...
数据集格式转换——json2txt、xml2txt、txt2json【复制就能用】
秋招面试专栏推荐 :深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转 💡💡💡本专栏所有程序均经过测试,可成功执行💡💡💡 专栏地址:YOLO11入门 + 改进涨点——点击即可跳转 欢迎订阅 目录 json2txt脚本 xml2txt txt2json...
Git常用操作之GitLab
Git常用操作之GitLab 小薛博客官网:小薛博客Git常用操作之GitLab官方地址 1、GitLab安装 https://gitlab.cn/install/ 1、Docker安装GitLab https://docs.gitlab.cn/jh/install/docker.html 1、设置卷位置 在设置其他所有内容之前,请配置一个新的…...
【商城实战(33)】解锁版本迭代与更新策略
【商城实战】专栏重磅来袭!这是一份专为开发者与电商从业者打造的超详细指南。从项目基础搭建,运用 uniapp、Element Plus、SpringBoot 搭建商城框架,到用户、商品、订单等核心模块开发,再到性能优化、安全加固、多端适配…...
强化学习 - PPO控制无人机
PPO(Proximal Policy Optimization,近端策略优化)是一种强化学习算法,用于训练智能体(无人机)如何在环境中做出决策。它本质上是 策略梯度(Policy Gradient)方法 的一种改进…...
QT 磁盘文件 教程03-创建、删除、复制文件
【1】新建文件 bool CreateFile(QString name){QString fileName name ; //"文件绝对路径名"QFile file(fileName);QFileInfo fileInfo(file);QString fileExtension fileInfo.suffix();if(fileExtension.isEmpty()) {fileName".txt";}file.setFileName…...
Netty基础—NIO的使用简介
1.Buffer缓冲区 (1)Buffer缓冲区的作用 在NIO中,所有的数据都是通过使用Buffer缓冲区来处理的。如果要通过NIO,将数据写到文件和网络或从文件和网络中读取数据,那么就需要使用Buffer缓冲区来进行处理。 (2)Buffer缓冲区的4个核心概念 Buffer缓…...
Matlab 汽车ABS实现模糊pid和pid控制
1、内容简介 Matlab 181-汽车ABS实现模糊pid和pid控制 可以交流、咨询、答疑 2、内容说明 略 实现汽车防抱死制动系统(ABS)的控制算法,通常涉及到传统的PID控制和模糊PID控制两种方法。下面将分别介绍这两种控制策略的基本概念以及如何在M…...
驾驭数据洪流:实时数据分析与流处理技术的革命
驾驭数据洪流:实时数据分析与流处理技术的革命 在现代社会,数据不再是冷冰冰的存储单位,而是实时运转的“生命体”。从电商交易监控、社交媒体互动,到自动驾驶决策,我们正被无数实时数据所包围。与此同时,实时数据分析和流处理技术应运而生,成为解决这一时代难题的关键…...
Muon: An optimizer for hidden layers in neural networks
引言 在深度学习领域,优化算法对模型训练效率和性能起着关键作用。从经典的随机梯度下降 (SGD) 及其动量法,到自适应优化方法 Adam/AdamW 等,一系列优化器大大加速了神经网络的收敛。然而,随着模型规模和数据量的爆炸式增长&…...
网络安全运维应急响应与溯源分析实战案例
在日常运维过程中,网络安全事件时有发生,快速响应和精准溯源是保障业务稳定运行的关键。本文将通过一个实际案例,详细解析从发现问题到溯源定位,再到最终解决的完整流程。 目录 一、事件背景 二、事件发现 1. 监控告警触发 2…...
【VSCODE 插件 可视化】:SVG 编辑插件 SVG Editor
插件下载 svgeditor 创建文件 Windows/Linux 快捷键 Ctrl Shift P 打开VSCODE 命令面板查找 New File With Svg Editor 编辑文件 保存文件 打开文件以继续编辑 CG 选中多个:shift单击没找到横向分布功能无法用键盘微调位置...
Cursor插件市场打不开解决
问题现象: cursor搜索插件的时候提示错误,无法搜索安装插件 error while fetching extensions.failed to fetch 问题原因 cursor默认安装使用的并不是vs code的插件市场,国内网络有时候打不开 解决 修改插件市场地址并重启cursor 打开cur…...
嵌入式开发之STM32学习笔记day06
基于STM32F103C8T6的开发实践——从入门到精通01 1. 引言 STM32系列微控制器是STMicroelectronics推出的一款高性能、低功耗的32位微控制器,广泛应用于嵌入式系统中。STM32F103C8T6是其中非常受欢迎的一款,凭借其强大的性能、丰富的外设接口和低廉的价格…...
