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

1.需求描述

需求类型:GIS点位标注

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

大概效果图如下:

2. 需求分析

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

3. 开发步骤

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

注:本例演示版本:vue@2.6.12 + leaflet@1.9.4

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,
    )
  })
},

至此,基本功能完成。

相关推荐
han_11 分钟前
JavaScript如何实现复制图片功能?
前端·javascript
崽崽的谷雨32 分钟前
react实现一个列表的拖拽排序(react实现拖拽)
前端·react.js·前端框架
小小坤1 小时前
前端基于AI生成H5 vue3 UI组件
前端·javascript·vue.js
既见君子1 小时前
透明视频
前端
竹等寒1 小时前
Go红队开发—web网络编程
开发语言·前端·网络·安全·web安全·golang
lhhbk1 小时前
angular中下载接口返回文件
前端·javascript·angular·angular.js
YUELEI1182 小时前
Vue使用ScreenFull插件实现全屏切换
前端·javascript·vue.js
我自纵横20232 小时前
第一章:欢迎来到 HTML 星球!
前端·html
发财哥fdy2 小时前
3.12-2 html
前端·html