leaflet怎么在地图上动态标记点位?

1.需求描述

需求类型:GIS点位标注

  1. 在地图上点击哪里,哪里就出现一个点位,且点位上出现一个输入框,用于输入点位名称;
  2. 输入框自动聚焦,只允许输入大小写英文、数字、符号,必填;
  3. 鼠标点击其他区域时,刚刚输入的点位添加到地图上,标点上方显示文本
  4. 标记的点位在左侧统计已添加的标记列表;
  5. 左侧删除了某个点位,地图上同步删除点位;
  6. 地图上有一个标记按钮,点击按钮后可在地图上添加点位,取消标记后点击地图无效
  7. 地图支持拖拽、缩放
  8. 最后,统一保存已添加的点位;

大概效果图如下:

2. 需求分析

  • 标记点位可以用L.marker()方法来实现;
  • 点位上显示的文本可以用bindTooltip()方法来绑定;
  • 输入框,由于leaflet没有直接支持,所以只能自定义了【麻烦】;
  • 点击其他区域时,输入的内容和点位合并显示在地图上,我们不能每次点击地图一次就生成一个点
    *
    1. 如果地图上无输入框的聚焦状态,则地图上添加一个点位;
      1. 如果有聚焦输入框,则不做操作,仅仅让它失去聚焦,否则体验感不好;
      1. 地图在拖拽时,没有触发点击事件,但是也会失去焦点,需要单独处理;
  • 在失去焦点时,需要做好几项判断:
    • 需要获取输入框的内容;
    • 需要判断这个点是否加在地图上;
    • 需要判断输入框的内容是否符合要求并给出提示;
    • 需要判断是在点击其余区域失焦的、还是拖拽失焦的。处理方式不一样;
  • 已添加的点位传给左侧的组件,这个不难;
  • 左侧的点位列表删除某个点位时,地图同步删除,理论上也不难;
  • 控制地图是否可点击的,加一个标记判断即可;

3. 开发步骤

以下省略地图初始化的相关代码,仅列出关键字段及方法

注:本例演示版本:[email protected] + [email protected]

3.1 基础数据声明

先在data()里声明一下变量

vue 复制代码
data() {
  return {
    // 新增点位的输入框是否聚焦,默认不聚焦,显示时才聚焦; true 为聚焦,false 为失去焦点
    isInputFocus: false, 
    // 输入框的点名称
    inputPointName: null, 
    // 是否允许标记点位,默认否
    isAllowEdit: false,
    // 地图上GIS点位列表
    GisPointList: [],
    // GIS点图层
    GISLayers: [],
    // GIS组
    GISGroup: [], 
    // 地图
    miniMapView: null,
    // 地图id
    id: null,
  }
}

3.2初始化地图

js 复制代码
initMap(){
    let mapOptions = {
      center: [40.222, 119.111], //初始地图中心
      zoom: 15, //初始缩放等级
      maxZoom: 18, //最大缩放等级
      minZoom: 13, //最小缩放等级
      zoomControl: false, //不显示缩放小控件
      attributionControl: false
    };
    this.miniMapView = new L.Map(this.id, mapOptions);
}

3.3 事件监听

3.3.1. 拖拽事件

如果当前为聚焦状态,把聚焦变为false,否则拖拽的时候触发了输入框失去焦点,但是变量isInputFocus还是true值......(此处为体验优化处理)

js 复制代码
this.miniMapView.on('drag', e => {
  if(this.isInputFocus) {
    this.isInputFocus = false
  }
})
3.3.2.点击事件
  • 如果当前为聚焦状态,把聚焦变为false,否则当你点击其他地方的时候,又会出现一个点位,不停地点不停地添加点位......(此处为体验优化处理)
  • 如果当前非聚焦状态,则进行点位添加;
kotlin 复制代码
this.miniMapView.on('click', e => {
  // 允许编辑时才生效
  if(this.isAllowEdit) {
    // 如果是聚焦的,点击别处时先失去焦点,让它变为失去焦点
    if(this.isInputFocus) {
      this.isInputFocus = false
    } else {
      // 不是聚焦状态,则新添加其他点
      const latlng = e.latlng
      this.addIconByMarkPoint(latlng, NormalPoint, [28, 36], new Date().getTime())
    }
  }
})

3.4 添加点位

用基本的L.marker()方法在地图上添加点位,传入坐标、点位图标、点位尺寸、点位id等信息

3.4.1 添加点位方法

还要自定义html内容,把它放入Popup方法中来显示,即下面声明的customPopupHtml,到那时这里有个问题,刚点击地图时、显示的点位及输入框是无法聚焦的,即使你在customPopupHtml中声明了autofocus属性也没用,所以需要特殊处理一下。

js 复制代码
/**
 *  标记点位,复制的函数来修改的
 * @param location 图标位置  [lat,lon]
 * @param iconUrl 图标类型及路径  ../icon/防爆2.png
 * @param iconSize  图标尺寸  [100,120]
 * @param pointId  图标id  不可重复
 */
addIconByMarkPoint(
  location,
  iconUrl,
  iconSize,
  pointId,
) {
  let markerIcon = L.icon({
    iconUrl: iconUrl,
    iconSize: iconSize,
  });
  this.normalUrl = iconUrl;
  this.iconSize = iconSize;
  let Marker = {};
  Marker.pointId = pointId;

  /** 自定义弹框内容  */
  const customPopupHtml = `<div class='popup-input'>
    <input type="text" placeholder="输入点位名称" style="width: 100%;" autofocus>
  </div>`

  // 是否点击了关闭按钮,因为点击击关闭按钮,会触发popupclose事件,所以需要一个变量来判断是否点击了关闭按钮
  let isClickCloseBtn = false
  const tempPopup = L.popup()
    .setLatLng(location)
    .setContent(customPopupHtml)
    .openOn(this.miniMapView)

  Marker = L.marker(location, { icon: markerIcon })
    .addTo(this.miniMapView)
    .bindPopup(tempPopup)
    .on('popupopen', () => {
      this.isInputFocus = true
    })
    .on('popupclose', () => {
      isClickCloseBtn = true
    })

  Marker.openPopup()
},
3.4.2 自动聚焦

既然无法聚焦,那就让它自动聚焦,加个延时就好了。并且把isInputFocus变量设置为true(因为多处需要监听)

ini 复制代码
setTimeout(() => {
  const inputElement = tempPopup.getElement().querySelector('input');
  this.isInputFocus = true
  if (inputElement) {
    inputElement.focus(); // 自动聚焦
  }
}, 80)
3.4.3 失去焦点处理

在上面自动聚焦后,需要对失去焦点进行监听。

监听时需要获取点位名称等基础信息、并且校验,如果不符合校验,把该点位删除

如果是符合规范的情况,失去焦点时,把该点添加进GISLayers,之后就需要把地图上所有的点位进行重绘了,因为这些点位没有bindTooltip,此处逻辑是先删除所有点位,再重新绘制带有bindTooltip的点位(此方法可能非最佳)

同时给父组件传递事件,再处理左侧列表的点位数据

kotlin 复制代码
inputElement.addEventListener('blur', () => {
  setTimeout(() => {
    this.inputPointName = inputElement.value
    if(!inputElement.value) {
      // 没有填写,则提示,且移除该点
      this.$message.error('请输入新标注点位名称!')
      this.miniMapView.removeLayer(Marker);
      return
    } else if(!inputRuleRegExp.test(inputElement.value)) {
      this.$message.error('只允许输入大小写英文、数字、符号')
      this.miniMapView.removeLayer(Marker);
      return
    } else {
      this.GisPointList.push({
        pointId: pointId,
        pointName: inputElement.value || null,
        lng: location.lng,
        lat: location.lat,
      })
      this.$emit('gisClick', {
        pointName: inputElement.value,
        lng: location.lng,
        lat: location.lat,
        pointId: pointId,
        isAdd: true
      })
    }
    // 如果点击了关闭按钮,删除这个临时点
    if(isClickCloseBtn) {
      this.GISGroup = L.layerGroup(this.GISLayers);
      this.miniMapView?.addLayer(this.GISGroup);
      this.removeGISPointById()
    } else {
      // 其他情况的失去焦点,地图添加点位
      this.GISLayers.push(Marker);
      this.GISGroup = L.layerGroup(this.GISLayers);
      this.miniMapView?.addLayer(this.GISGroup);
      // this.miniMapView.removeLayer(Marker);
      this.removeGISPointById(pointId)
    }
  }, 200)
  // 这里可以添加失去焦点后的逻辑
  this.miniMapView.removeLayer(Marker);
});
3.4.4 删除指定点位

也可用于左侧列表的删除,根据id删除地图上的点位

kotlin 复制代码
/**
 * 删除指定id的点位,其他点位重画
 * */
removeGISPointById(pointId) {
  // const tempArr = this.GisPointList.filter(item => item.pointId !== pointId)
  const tempArr = pointId ? this.GisPointList.filter(item => item.pointId !== pointId) : this.GisPointList

  //处理添加的marker图标组
  this.GISLayers.splice(0, this.GisPointList.length);
  //清除marker图层组
  this.GISGroup.clearLayers();
  this.GisPointList = tempArr

  tempArr.forEach(item => {
    this.reDrawGISPoint(
      [item.lat, item.lng],
      NormalPoint,
      [28, 32],
      item.pointId,
      item.pointName,
    )
  })
},

至此,基本功能完成。

相关推荐
刺客-Andy9 分钟前
React 第三十九节 React Router 中的 unstable_usePrompt Hook的详细用法及案例
前端·javascript·react.js
Go_going_15 分钟前
【js基础笔记] - 包含es6 类的使用
前端·javascript·笔记
浩~~1 小时前
HTML5 浮动(Float)详解
前端·html·html5
AI大模型顾潇2 小时前
[特殊字符] 本地大模型编程实战(29):用大语言模型LLM查询图数据库NEO4J(2)
前端·数据库·人工智能·语言模型·自然语言处理·prompt·neo4j
九月TTS2 小时前
TTS-Web-Vue系列:Vue3实现内嵌iframe文档显示功能
前端·javascript·vue.js
爱编程的小学究2 小时前
【node】如何把包发布到npm上
前端·npm·node.js
weixin_473894773 小时前
前端服务器部署分类总结
前端·网络·性能优化
LuckyLay3 小时前
React百日学习计划-Grok3
前端·学习·react.js
澄江静如练_3 小时前
小程序 存存上下滑动的页面
前端·javascript·vue.js
互联网搬砖老肖3 小时前
Web 架构之会话保持深度解析
前端·架构