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()里声明一下变量

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

3.2初始化地图

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值……(此处为体验优化处理)

this.miniMapView.on('drag', e => {
    
    
  if(this.isInputFocus) {
    
    
    this.isInputFocus = false
  }
})
3.3.2.点击事件
  • 如果当前为聚焦状态,把聚焦变为false,否则当你点击其他地方的时候,又会出现一个点位,不停地点不停地添加点位……(此处为体验优化处理)
  • 如果当前非聚焦状态,则进行点位添加;
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属性也没用,所以需要特殊处理一下。

/**
 *  标记点位,复制的函数来修改的
 * @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(因为多处需要监听)

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

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

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

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

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

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删除地图上的点位

/**
 * 删除指定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,
    )
  })
},

至此,基本功能完成。

猜你喜欢

转载自blog.csdn.net/yan1915766026/article/details/146230802
今日推荐