leaflet学习笔记-贝塞尔曲线绘制(八)

前言

两点之间的连线是很常见的,但是都是直直的一条线段,为了使连线更加平滑,我们可以使用曲线进行连线,本功能考虑使用贝塞尔曲线进行连线绘制,最后将线段的两端节点连接,返回一个polygon。

贝塞尔简介

给定不同的点 P0 和 P1,线性贝塞尔曲线只是这两点之间的一条线。

相当于线性插值。

Turf.bezierSpline()简介

接受一条线,通过应用贝塞尔样条算法返回一个弯曲的版本

官方例子

var line = turf.lineString([
  [-76.091308, 18.427501],
  [-76.695556, 18.729501],
  [-76.552734, 19.40443],
  [-74.61914, 19.134789],
  [-73.652343, 20.07657],
  [-73.157958, 20.210656]
]);

var curved = turf.bezierSpline(line);

参数说明

line:input LineString

options:{

resolution:点之间的时间(毫秒)

sharpness:衡量样条路径应该有多弯曲的一个度量

}

具体可以查看官方的bezierSpline函数

UseBezierSpline.js完整代码

import { onBeforeUnmount, reactive, ref } from 'vue'
import * as turf from '@turf/turf'

/**
 * @Description 接受一条线,通过应用贝塞尔样条算法返回一个弯曲的版本(贝塞尔曲线)
 * @param mainMap 地图对象
 * @param drawComplete 完成绘制的回调函数
 * @Param bezierOptions
 * @Param bezierOptions.resolution 点之间的时间(毫秒)
 * @Param bezierOptions.sharpness 衡量样条路径应该有多弯曲的一个度量
 * @param drawLayer 绘制图形的layer
 * @Author ZhangJun
 * @Date  2024-01-11 09:55:12
 * @return void
 **/
export default function useBezierSpline(mainMap, drawComplete = null, bezierOptions = {}, drawLayer = undefined) {
  //默认的贝塞尔配置参数合并
  let bezierOptions_config = reactive({
    resolution: 1000,
    sharpness: 0.85,
    ...bezierOptions,
  })

  //绘制事件状态
  let status = ref('start')

  // 记录当前状态是否可以拖动绘制
  let isDragging = true

  //拖动绘制的坐标
  let drawPath = []

  //拖动绘制的线
  let drawLine = reactive(null)

  //鼠标正在移动的点
  let movePoint

  //最终绘制的polygon,最终返回的就是它的轮廓坐标
  let polygon_draw = reactive(null)

  //鼠标提示
  let mouseEventPopup = new L.popup({ className: 'customPopup', closeButton: false })

  if (mainMap) {
    //关闭的时候一定要销毁
    onBeforeUnmount(() => {
      closeDraw()
      mainMap?.removeLayer(drawLayer)
    })

    if (!drawLayer) {
      drawLayer = L.featureGroup([])
      drawLayer.addTo(mainMap)
    }

    //初始化事件
    let initEvents = () => {
      isDragging = false

      //按下鼠标确定需要添加的节点(暂停中)
      mainMap.on('mousedown', (e) => {
        isDragging = false
        let { lat, lng } = e.latlng
        drawPath = [...drawPath, [lng, lat]]

        //如果才开始点击第一次,就创建一个polyline,后面需要动态需改它的path
        if (drawPath?.length === 1) {
          status.value = 'start'
          //添加绘制line
          drawLine = L.polyline(drawPath, { color: 'red' }).addTo(mainMap)
        }
      })

      //松开鼠标开始拖动绘制(开始绘制)
      mainMap.on('mouseup', (e) => {
        isDragging = true
      })

      //鼠标移动绘制(绘制中)
      mainMap.on('mousemove', (e) => {
        if (isDragging) {
          let { lat, lng } = e.latlng
          movePoint = [lng, lat]
          //动态生成贝塞尔曲线的feature
          let splineFeature = generationBezierSpline(drawPath, movePoint)
          if (splineFeature) {
            let tempCoords = turf.getCoords(turf.flip(splineFeature))
            //将生成的贝塞尔曲线的坐标传给polyline,在地图上刷新渲染
            drawLine?.setLatLngs(tempCoords)
          }
        }
        mouseEventPopup?.setLatLng(e.latlng)?.setContent('右键结束绘制')

        //如果还没有添加就直接先添加一下
        if (!mainMap.hasLayer(mouseEventPopup)) {
          //打开方向的popup
          mouseEventPopup?.openOn(mainMap)
        }
      })

      //右键结束(结束绘制)
      mainMap.on('contextmenu', (e) => {
        let coords = turf.getCoords(turf.flip(drawLine.toGeoJSON()))
        //生成polygon
        polygon_draw = L.polygon(coords, { color: 'green' })

        clearDrawLayer()
        addLayersToDrawLayer([polygon_draw])
        //移除曲线
        mainMap?.removeLayer(drawLine)

        status.value = 'end'
        //绘制完成的回调
        if (typeof drawComplete === 'function') {
          let result = getResult(polygon_draw)
          drawComplete(result)
        }
      })

    }

    //移除事件
    let removeEvents = () => {
      //按下鼠标
      mainMap?.off('mousedown')
      //抬起鼠标
      mainMap?.off('mouseup')
      //拖拽事件
      mainMap?.off('mousemove')
      //右键事件
      mainMap?.off('contextmenu')
    }

    //开始绘制
    let startDraw = () => {
      //禁止拖动地图
      mainMap?.dragging?.disable()
      //初始化事件
      initEvents()
    }

    //清除原来绘制的内容
    let clearDrawLayer = () => {
      drawPath = []
      drawLayer?.clearLayers()
    }

    //添加要素到drawLayer
    let addLayersToDrawLayer = (features = []) => {
      features?.forEach(feature => {
        drawLayer.addLayer(feature)
      })
    }

    /**
     * @Description 生成曲线
     * @Param originalPath 已经确定的点坐标集合
     * @Param lastPoint 最后一个坐标点,一般为移动的点坐标
     * @Author ZhangJun
     * @Date  2024-01-11 10:36:11
     * @return void
     **/
    let generationBezierSpline = (originalPath = drawPath, lastPoint = movePoint) => {
      if (originalPath?.length > 0) {
        //加入最后一个点
        let line = turf.lineString([...originalPath, lastPoint], bezierOptions_config)
        return turf.bezierSpline(line)
      }

      return null
    }

    //关闭绘制功能
    let closeDraw = () => {
      //清空绘制的几何
      clearDrawLayer()
      //一定要移除事件,否则事件之间会有干扰
      removeEvents()

      //移除popup
      mainMap?.closePopup(mouseEventPopup)

      //激活拖拽功能
      mainMap?.dragging?.enable()
    }

    //获取最终的polygon的轮廓坐标
    let getResult = (feature = polygon_draw) => {
      if (feature) {
        //获取输入 feature 并将它们的所有坐标从 [x, y] 翻转为 [y, x]。
        let featureCollection = turf.flip(feature.toGeoJSON())
        return turf.getCoords(featureCollection)
      }
      return []
    }

    return { status, getResult, closeDraw, drawLayer, bezierOptions_config, startDraw }
  }

  return {}
}

UseBezierSpline.js使用

if (wizMap?.map) {
  let { startDraw, closeDraw, status: temp, getResult } = useBezierSpline(wizMap?.map)
  getCoords = getResult
  //当前绘制状态(是否完成绘制)
  status.value = temp

  onMounted(() => {
    //需要这种处理,不然会有异常
    nextTick(() => {
      startDraw()
    })
  })
}

效果

贝塞尔曲线

加强版

添加了质心的绘制,并且可以拖动质心位置

完整代码

import { onBeforeUnmount, reactive, ref, watch } from 'vue'
import * as turf from '@turf/turf'
import L from 'leaflet'

/**
 * @Description 接受一条线,通过应用贝塞尔样条算法返回一个弯曲的版本(贝塞尔曲线)
 * @param mainMap 地图对象
 * @param drawComplete 完成绘制的回调函数
 * @param isShowCenterPoint 是否显示中心点
 * @Param bezierOptions
 * @Param bezierOptions.resolution 点之间的时间(毫秒)
 * @Param bezierOptions.sharpness 衡量样条路径应该有多弯曲的一个度量
 * @param drawLayer 绘制图形的layer
 * @Author ZhangJun
 * @Date  2024-01-11 09:55:12
 * @return void
 **/
export default function useBezierSpline(mainMap, drawComplete = null, isShowCenterPoint = false, bezierOptions = {}, drawLayer = undefined) {
  //默认的贝塞尔配置参数合并
  let bezierOptions_config = reactive({
    resolution: 1000,
    sharpness: 0.85,
    ...bezierOptions,
  })

  //绘制事件状态
  let status = ref('start')
  //中心点(质心坐标)
  let center = ref([])

  // 记录当前状态是否可以拖动绘制
  let isDragging = true

  //拖动绘制的坐标
  let drawPath = []

  //拖动绘制的线
  let drawLine = reactive(null)

  //鼠标正在移动的点
  let movePoint

  //最终绘制的polygon,最终返回的就是它的轮廓坐标
  let polygon_draw = ref(null)

  //中心点的标记配置
  let divIconConfig = ref({
    className: 'center-icon',
    html: '<div style="background-color: red;height: 10px;width: 10px"></div>',
  })

  //鼠标提示
  let mouseEventPopup = new L.popup({ className: 'customPopup', closeButton: false })

  if (mainMap) {
    //关闭的时候一定要销毁
    onBeforeUnmount(() => {
      closeDraw()
      mainMap?.removeLayer(drawLayer)
    })

    if (!drawLayer) {
      drawLayer = L.featureGroup([])
      drawLayer.addTo(mainMap)
    }

    //初始化事件
    let initEvents = () => {
      isDragging = false

      //按下鼠标确定需要添加的节点(暂停中)
      mainMap.on('mousedown', e => {
        isDragging = false
        let { lat, lng } = e.latlng
        drawPath = [...drawPath, [lng, lat]]

        //如果才开始点击第一次,就创建一个polyline,后面需要动态需改它的path
        if (drawPath?.length === 1) {
          status.value = 'start'
          //添加绘制line
          drawLine = L.polyline(drawPath, { color: 'red' }).addTo(mainMap)
        }
      })

      //松开鼠标开始拖动绘制(开始绘制)
      mainMap.on('mouseup', e => {
        isDragging = true
      })

      //鼠标移动绘制(绘制中)
      mainMap.on('mousemove', e => {
        if (isDragging) {
          let { lat, lng } = e.latlng
          movePoint = [lng, lat]
          //动态生成贝塞尔曲线的feature
          let splineFeature = generationBezierSpline(drawPath, movePoint)
          if (splineFeature) {
            let tempCoords = turf.getCoords(turf.flip(splineFeature))
            //将生成的贝塞尔曲线的坐标传给polyline,在地图上刷新渲染
            drawLine?.setLatLngs(tempCoords)
          }
        }
        mouseEventPopup?.setLatLng(e.latlng)?.setContent(status.value==='start'?'右键结束绘制':'点击开始绘制')

        //如果还没有添加就直接先添加一下
        if (!mainMap.hasLayer(mouseEventPopup)) {
          //打开方向的popup
          mouseEventPopup?.openOn(mainMap)
        }
      })

      //右键结束(结束绘制)
      mainMap.on('contextmenu', e => {
        isDragging=false
        let coords = turf.getCoords(turf.flip(drawLine.toGeoJSON()))
        //生成polygon
        polygon_draw.value = L.polygon(coords, { color: 'green' })

        clearDrawLayer()
        addLayersToDrawLayer([polygon_draw.value])
        //移除曲线
        mainMap?.removeLayer(drawLine)

        status.value = 'end'
        //绘制完成的回调
        if (typeof drawComplete === 'function') {
          let result = getResult(polygon_draw.value)
          drawComplete(result)
        }

        //如果需要展示中心点
        if (isShowCenterPoint) {
          let centroid = turf.centroid(polygon_draw.value.toGeoJSON())
          let myIcon = L.divIcon(divIconConfig.value)

          //质心赋值
          center.value = turf.getCoord(turf.flip(centroid))

          //质心添加
          let centerMarker=   L.marker(center.value, { icon: myIcon ,draggable: true}).addTo(drawLayer)

          //拖动质心就禁止点击事件
          centerMarker.on('dragstart', function(e) {
            isDragging=false
            drawLine?.remove()
            drawPath=[];
            status.value = 'end'
            //移除popup
            mainMap?.closePopup(mouseEventPopup)
           removeEvents()
          });

          centerMarker.on('dragend', function(e) {
            let {lat,lng}=e?.target?.getLatLng()
            center.value=[lat,lng]
            //打开方向的popup
            mouseEventPopup?.openOn(mainMap)
            initEvents()
            isDragging=true
          });
        }
      })
    }

    //移除事件
    let removeEvents = () => {
      //按下鼠标
      mainMap?.off('mousedown')
      //抬起鼠标
      mainMap?.off('mouseup')
      //拖拽事件
      mainMap?.off('mousemove')
      //右键事件
      mainMap?.off('contextmenu')
    }

    //开始绘制
    let startDraw = () => {
      //禁止拖动地图
      mainMap?.dragging?.disable()
      //初始化事件
      initEvents()
    }

    //清除原来绘制的内容
    let clearDrawLayer = () => {
      drawPath = []
      drawLayer?.clearLayers()
    }

    //添加要素到drawLayer
    let addLayersToDrawLayer = (features = []) => {
      features?.forEach(feature => {
        drawLayer.addLayer(feature)
      })
    }

    /**
     * @Description 生成曲线
     * @Param originalPath 已经确定的点坐标集合
     * @Param lastPoint 最后一个坐标点,一般为移动的点坐标
     * @Author ZhangJun
     * @Date  2024-01-11 10:36:11
     * @return void
     **/
    let generationBezierSpline = (originalPath = drawPath, lastPoint = movePoint) => {
      if (originalPath?.length > 0) {
        //加入最后一个点
        let line = turf.lineString([...originalPath, lastPoint], bezierOptions_config)
        return turf.bezierSpline(line)
      }

      return null
    }

    //关闭绘制功能
    let closeDraw = () => {
      //清空绘制的几何
      clearDrawLayer()
      //一定要移除事件,否则事件之间会有干扰
      removeEvents()

      //移除popup
      mainMap?.closePopup(mouseEventPopup)

      //激活拖拽功能
      mainMap?.dragging?.enable()
    }

    //获取最终的polygon的轮廓坐标
    let getResult = (feature = polygon_draw.value) => {
      if (feature) {
        //获取输入 feature 并将它们的所有坐标从 [x, y] 翻转为 [y, x]。
        let featureCollection = turf.flip(feature.toGeoJSON())
        return turf.getCoords(featureCollection)
      }
      return []
    }

    return {
      status,
      getResult,
      closeDraw,
      drawLayer,
      bezierOptions_config,
      startDraw,
      polygon_draw,
      divIconConfig,
      center,
    }
  }

  return {}
}


本文为学习笔记,仅供参考

  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

花姐夫Jun

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值