Vue3组件+leaflet,实现重叠marker的Popup切换显示

news2025/3/19 1:02:37

一、前言

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 = null
  markerIndex.value = 0
  currentMarkerContent.value = {}
}

//这里就是将marker的icon更新,跟popup显示的image一致
watchEffect(() => {
  //marker不能为null
  if (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的iconUrl
    if (iconUrl && iconUrl !== icon.options.iconUrl) {
      icon.options.iconUrl = iconUrl
      targetMarker.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 = res
      drawWarningSignalMarkers(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 } = item
        let 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] = markerObjects
        markerObject
          .bindPopup(
            e => {
              //先重置popup内容
              warningSignalPopupRef.value.reset()
              //设置图标对象
              warningSignalPopupRef.value.targetMarker = markerObject
              //设置弹窗内容列表
              warningSignalPopupRef.value.popupContentList = markerObjects.map(({ attributes }) => attributes)
              //返回弹窗组件实例,这里是vue组件实例,所以需要用$el来获取dom
              return 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文件查询后填充。

四、效果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2317500.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

机器学习之距离度量方法

常见的距离度量方法及相关函数、图示如下: 1. 欧几里得距离(Euclidean Distance) 函数公式:对于两个 ( n ) 维向量 ( x = ( x 1 , x 2 , ⋯   ,

3.1 在VisionPro脚本中添加CogGraphicLabel

本案例需要实现如下功能&#xff1a; 1.加载toolBlock 2.加载图片&#xff0c; 3.运行Block 4.VisionPro中添加脚本显示数值。 见下图&#xff1a;详细代码&#xff08;C#以及visionPro&#xff09;见下面链接&#xff1a; https://download.csdn.net/download/qq_340474…

AI:Machine Learning Data Science

机器学习与数据科学 左侧 机器学习 Machine Learning 机器学习是一门多领域交叉学科&#xff0c;涉及概率论、统计学、逼近论、凸分析、算法复杂度理论等多门学科。专门研究计算机怎样模拟或实现人类的学习行为&#xff0c;以获取新的知识或技能&#xff0c;重新组织已有的知…

软件需求分类、需求获取(高软46)

系列文章目录 软件需求分类&#xff0c;需求获取 文章目录 系列文章目录前言一、软件需求二、获取需求三、真题总结 前言 本节讲明软件需求分类、需求获取的相关知识。 一、软件需求 二、获取需求 三、真题 总结 就是高软笔记&#xff0c;大佬请略过&#xff01;

嵌入式Linux | 什么是 BootLoader、Linux 内核(kernel)、和文件系统?

01 什么是 BootLoader 呢&#xff1f; 它是个引导程序&#xff0c;也就是硬件复位以后第一个要执行的程序&#xff0c;它主要工作就是初始化操作系统运行的环境&#xff0c;比如说内存、定时器、缓冲器等&#xff0c;当这个工作做完以后&#xff0c;再把操作系统的代码加载…

函数(函数的概念、库函数、自定义函数、形参和实参、return语句、数组做函数参数、嵌套调用和链式访问、函数的声明和定义、static和extern)

一、函数的概念 •C语⾔中的函数&#xff1a;⼀个完成某项特定的任务的⼀⼩段代码 •函数又被翻译为子函数&#xff08;更准确&#xff09; •在C语⾔中我们⼀般会⻅到两类函数&#xff1a;库函数 ⾃定义函数 二、库函数 1 .标准库和头文件 •C语⾔的国际标准ANSIC规定了⼀…

ImGui 学习笔记(五) —— 字体文件加载问题

ImGui 加载字体文件的函数似乎存在编码问题&#xff0c;这一点可能跟源文件的编码也有关系&#xff0c;我目前源文件编码是 UTF-16。 当参数中包含中文字符时&#xff0c;ImGui 内部将字符转换为宽字符字符集时候&#xff0c;采用的 MultiByteToWideChar API 参数不太对&#…

OpenCV计算摄影学(20)非真实感渲染之增强图像的细节函数detailEnhance()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 此滤波器增强特定图像的细节。 cv::detailEnhance用于增强图像的细节&#xff0c;通过结合空间域和频率域的处理&#xff0c;提升图像中特定细节…

Android PC 要来了?Android 16 Beta3 出现 Enable desktop experience features 选项

在之前的 《Android 桌面窗口新功能推进》 我们就聊过&#xff0c;Google 就一直在努力改进 Android 的内置桌面模式&#xff0c;例如添加了适当的窗口标题、捕捉窗口的能力、悬停选项、窗口大小调整、最小化支持、app-to-web 等。 比如在搭载 Android 15 QPR 1 Beta 2 的 Pix…

Git常用操作之GitLab

Git常用操作之GitLab 小薛博客官网&#xff1a;小薛博客Git常用操作之GitLab官方地址 1、GitLab安装 https://gitlab.cn/install/ 1、Docker安装GitLab https://docs.gitlab.cn/jh/install/docker.html 1、设置卷位置 在设置其他所有内容之前&#xff0c;请配置一个新的…

Netty基础—NIO的使用简介

1.Buffer缓冲区 (1)Buffer缓冲区的作用 在NIO中&#xff0c;所有的数据都是通过使用Buffer缓冲区来处理的。如果要通过NIO&#xff0c;将数据写到文件和网络或从文件和网络中读取数据&#xff0c;那么就需要使用Buffer缓冲区来进行处理。 (2)Buffer缓冲区的4个核心概念 Buffer缓…

Matlab 汽车ABS实现模糊pid和pid控制

1、内容简介 Matlab 181-汽车ABS实现模糊pid和pid控制 可以交流、咨询、答疑 2、内容说明 略 实现汽车防抱死制动系统&#xff08;ABS&#xff09;的控制算法&#xff0c;通常涉及到传统的PID控制和模糊PID控制两种方法。下面将分别介绍这两种控制策略的基本概念以及如何在M…

Muon: An optimizer for hidden layers in neural networks

引言 在深度学习领域&#xff0c;优化算法对模型训练效率和性能起着关键作用。从经典的随机梯度下降 (SGD) 及其动量法&#xff0c;到自适应优化方法 Adam/AdamW 等&#xff0c;一系列优化器大大加速了神经网络的收敛。然而&#xff0c;随着模型规模和数据量的爆炸式增长&…

【VSCODE 插件 可视化】:SVG 编辑插件 SVG Editor

插件下载 svgeditor 创建文件 Windows/Linux 快捷键 Ctrl Shift P 打开VSCODE 命令面板查找 New File With Svg Editor 编辑文件 保存文件 打开文件以继续编辑 CG 选中多个&#xff1a;shift单击没找到横向分布功能无法用键盘微调位置

Cursor插件市场打不开解决

问题现象&#xff1a; cursor搜索插件的时候提示错误&#xff0c;无法搜索安装插件 error while fetching extensions.failed to fetch 问题原因 cursor默认安装使用的并不是vs code的插件市场&#xff0c;国内网络有时候打不开 解决 修改插件市场地址并重启cursor 打开cur…

嵌入式开发之STM32学习笔记day06

基于STM32F103C8T6的开发实践——从入门到精通01 1. 引言 STM32系列微控制器是STMicroelectronics推出的一款高性能、低功耗的32位微控制器&#xff0c;广泛应用于嵌入式系统中。STM32F103C8T6是其中非常受欢迎的一款&#xff0c;凭借其强大的性能、丰富的外设接口和低廉的价格…

AI驱动的视频字幕提取与翻译工具

青梧字幕是一款基于Whisper技术的AI字幕提取工具&#xff0c;专为视频制作者、翻译人员和自媒体创作者设计。它通过先进的语音识别算法&#xff0c;能够自动从视频文件中提取字幕内容&#xff0c;并支持多种语言和字幕格式&#xff0c;极大地简化了字幕制作流程。 目前暂支持 …

【MySQL】MySQL审计工具Audit Plugin安装使用

MySQL审计工具Audit Plugin安装使用 https://www.cnblogs.com/waynechou/p/mysql_audit.html MySQL 5.6 开启审计功能 https://blog.51cto.com/u_15127556/4344503 MySQL之添加日志审计功能 https://blog.csdn.net/weixin_43279032/article/details/105507170 MySQL开启日志记录…

游戏引擎学习第163天

我们可以在资源处理器中使用库 因为我们的资源处理器并不是游戏的一部分&#xff0c;所以它可以使用库。我说过我不介意让它使用库&#xff0c;而我提到这个的原因是&#xff0c;今天我们确实有一个选择——可以使用库。 生成字体位图的两种方式&#xff1a;求助于 Windows 或…

用python代码将excel中的数据批量写入Json中的某个字段,生成新的Json文件

需求 需求&#xff1a; 1.将execl文件中的A列赋值给json中的TrackId&#xff0c;B列赋值给json中的OId 要求 execl的每一行&#xff0c;对应json中的每一个OId json 如下&#xff1a; {"List": [{"BatchNumber": "181-{{var}}",// "Bat…