leaflet学习笔记-缓冲区绘制(六)

前言

在GIS开发中,缓冲区的绘制和使用是非常广泛的,一般情况下就是对缓冲区范围内的要素做分析使用,也会有一些其他的操作,下面我就记录一下使用leaflet+turf.js完成缓冲区的绘制操作

turf.js简介

Turf.js 是一个用于地理空间计算的 JavaScript 库。它提供了许多地理空间操作的函数,如点线面的创建、缓冲区计算、距离计算、区域合并等,方便在前端应用中处理地理空间数据和实现地图相关功能。Turf.js 不依赖于任何地图库,可以在任何 JavaScript 环境中使用。

绘制缓冲区主要使用buffer方法函数返回缓冲后的几何数据,官网例子如下

var point = turf.point([-90.548630, 14.616599]);
var buffered = turf.buffer(point, 500, {units: 'miles'});

其中需要传入的参数

geojson:要进行缓冲的输入

radius:绘制缓冲区的距离(允许负值)

options:{

units:turf库单位支持的任何选项(“meters” | “millimeters” | “centimeters” | “kilometers” | “acres” | “miles” | “nauticalmiles” | “inches” | “yards” | “feet” | “radians” | “degrees” | “hectares”

steps:频数

}

原理

绘制缓冲区的操作流程是按压拖拽鼠标,监听鼠标移动轨迹并绘制polyline,当松开鼠标的时候就进行缓冲区的绘制;所以我们在这个操作流程中需要获取拖拽产生的drawLine,并将它作为(geojson)输入项传到turf.js的buffer函数中,添加缓冲距离(radius)和单位(units)最后得到缓冲后的数据,并将它加入一些样式后添加到地图上显示。

代码

直接上代码,按上面的操作流程和代码注释很容易理解

UseBuffer.js完整代码

/*** @ClassName UseBuffer.js* @Description 用于缓冲区的操作使用* @Author ZhangJun* @Date  2024/1/8 13:12**/
import 'leaflet.pm'
import 'leaflet.pm/dist/leaflet.pm.css'
import * as turf from '@turf/turf'
import { onUnmounted, reactive, ref } from 'vue'export default function useBuffer(mainMap, distance = 0, units = 'kilometers', geometryType = 'Line', drawLayer=null) {//绘制事件状态let status = ref('start')// 记录当前状态为按住状态let isDragging = true//拖动绘制的坐标let drawPath = []//拖动绘制的线let drawLine = reactive(null)//缓冲后的featurelet bufferFeature = reactive(null)//鼠标提示let mouseEventPopup = new L.popup({ className: 'customPopup', closeButton: false })if (mainMap) {//关闭的时候一定要销毁onUnmounted(() => {closeDraw()mainMap?.removeLayer(drawLayer)})if (!drawLayer) {drawLayer = L.featureGroup([])drawLayer.addTo(mainMap)}//初始化事件let initEvents = () => {isDragging = false//按下鼠标开始拖拽mainMap.on('mousedown', (e) => {isDragging = true//清空原来的绘制路径drawPath = []//添加绘制linedrawLine = L.polyline(drawPath, { color: 'red' }).addTo(mainMap)})mainMap.on('mousemove', (e) => {if (isDragging) {let { lat, lng } = e.latlngdrawPath = [...drawPath, [lat, lng]]drawLine?.setLatLngs(drawPath)}mouseEventPopup?.setLatLng(e.latlng)?.setContent(isDragging ? '鼠标抬起完成绘制' : '鼠标按压拖拽绘制')//如果还没有添加就直接先添加一下if (!mainMap.hasLayer(mouseEventPopup)) {//打开方向的popupmouseEventPopup?.openOn(mainMap)}})//松开鼠标结束拖拽(绘制结束)mainMap.on('mouseup', (e) => {status.value = 'end'isDragging = false// mainMap.off('mousemove')mainMap?.removeLayer(drawLine)//通过绘制的polyline获取缓冲的featurerefreshBuffer()})}//移除事件let removeEvents = () => {//按下鼠标mainMap?.off('mousedown')//抬起鼠标mainMap?.off('mouseup')//结束拖拽事件mainMap?.off('mousemove')}//开始绘制let startDraw = (type = geometryType) => {//禁止拖动地图mainMap?.dragging?.disable()//初始化事件initEvents()}//清除原来绘制的内容let clearDrawLayer = () => {drawLayer?.clearLayers()}//添加要素到drawLayerlet addLayersToDrawLayer = (features = []) => {features?.forEach(feature => {drawLayer.addLayer(feature)})}/*** @Description 刷新缓冲区* @Param distance1 缓冲距离* @Param units1 缓冲区单位* @Author ZhangJun* @Date  2024-01-08 05:43:01* @return void**/let refreshBuffer = (distance1 = distance, units1 = units) => {if (distance1 !== distance) {distance = distance1}if (units1 !== units) {units = units1}let sourceGeoJSON = drawLine?.toGeoJSON()if (sourceGeoJSON) {bufferFeature?.remove()bufferFeature = generationBuffer(sourceGeoJSON, distance1, units1)drawLayer?.clearLayers()addLayersToDrawLayer([bufferFeature])}}//关闭绘制功能let closeDraw = () => {//清空绘制的几何clearDrawLayer()//一定要移除事件,否则事件之间会有干扰removeEvents()//激活拖拽功能mainMap?.dragging?.enable()//移除popupmainMap?.closePopup(mouseEventPopup)}//获取缓冲区的坐标集合let getBufferCoords = () => {if (bufferFeature) {//获取输入 feature 并将它们的所有坐标从 [x, y] 翻转为 [y, x]。let featureCollection = turf.flip(bufferFeature.toGeoJSON())return featureCollection?.features?.map(feature => turf.getCoords(feature))}return []}return { status, refreshBuffer, getBufferCoords, closeDraw, drawLayer, startDraw }}return {}
}/*** @Description 生成缓冲区的geoJson* @Param sourceGeoJSON 需要被进行缓冲分析的geoJson* @Param distance 缓冲距离* @Param units 缓冲范围的距离单位("meters" | "millimeters" | "centimeters" | "kilometers" | "acres" | "miles" | "nauticalmiles" | "inches" | "yards" | "feet" | "radians" | "degrees" | "hectares")* @Param bufferStyle 缓冲后的样式* @Author ZhangJun* @Date  2024-01-08 02:37:05**/
function generationBuffer(sourceGeoJSON, distance = 0, units = 'meters', bufferStyle = {color: 'green',dashArray: [5, 5],weight: 2,fillColor: 'green',fillOpacity: 0.2,
}) {if (sourceGeoJSON) {//生成缓冲后的geoJSONconst buffered = turf.buffer(sourceGeoJSON, distance, {units,})if (buffered) {return L.geoJSON(buffered, {style: function(feature) {return bufferStyle},})}}return null
}

代码结构是我学习vue3的自定义hook的写法,用起来很好用

UseBuffer.js使用

代码如下

<template><el-form ref="ruleFormRef" class="p-2" :model="ruleForm" status-icon label-width="80px"><el-form-item label="影响半径" prop="winRadius"><el-input-number v-model="ruleForm.winRadius" :min="0" class="mr-1"></el-input-number><el-text>公里</el-text></el-form-item><el-form-item><el-button type="success" :disabled="status.value !== 'end'" @click="submitForm(ruleFormRef)"><el-icon :size="20" style="margin-right: 5px"><CircleCheckFilled /></el-icon>应用</el-button></el-form-item></el-form>
</template><script setup>
import { ref, reactive, watch, onUnmounted } from 'vue'
import { useStore } from 'vuex'
import { CircleCheckFilled } from '@element-plus/icons-vue'
import useBuffer from '/public/js/UseBuffer'let store = useStore()const ruleFormRef = ref(null) // 注意:一定要定义 form 表单中 ref 的 ruleFormRef 的值,否则会一直报错;const ruleForm = reactive({winRadius: 10,
})let status = ref('')let getBufferCoords = reactive(null)//添加缓冲区的函数
let addBuffer = reactive(null)let closeDraw = null// 此时是:提交表单的操作;
const submitForm = () => {if (!ruleFormRef.value) returnruleFormRef.value.validate(valid => {// 注意:此时使用的是 ruleFormRef.value,而仅写 ruleFormRef 是拿不到值且会报错的;if (valid) {// 注意:只有当所有的规则都满足后,此时的 valid 的值才为 true,才能执行下面的值;console.log('submit!')alert(JSON.stringify(getBufferCoords()))} else {console.log('error submit!')return false}})
}const wiz_map = store.getters.GET_WIZ_MAP
if (wiz_map?.map) {let { refreshBuffer, status: temp, getBufferCoords: getCoords, closeDraw: close, startDraw } = useBuffer(wiz_map?.map, ruleForm.winRadius)//当前绘制状态(是否完成绘制)status.value = temp//刷新绘制缓冲区addBuffer = refreshBuffer//获取缓冲区的坐标集合getBufferCoords = getCoords//关闭绘制功能函数closeDraw = closestartDraw()
}//监听缓冲半径的变化,进行缓冲区刷新
watch(() => ruleForm.winRadius,(newVal, oldVal) => {if (newVal !== oldVal) {addBuffer(newVal)}},
)
</script><style lang="scss" scoped>
.el-input-group {width: 130px !important;
}
</style>

由于为了提示操作方式,我添加了自定义的popup,并添加了一个class给这个popup

 let mouseEventPopup = new L.popup({ className: 'customPopup', closeButton: false })

所以需要添加一个全局的样式,改变默认的popup的样式

<style lang='scss'>
/*自定义leafLet的popup的样式*/
.customPopup {.leaflet-popup-content-wrapper {background: rgba(255, 255, 255, 0.8);border-radius: 4px;.leaflet-popup-content {margin: 6px;}}.leaflet-popup-tip {background: rgba(255, 255, 255, 0.8);}
}
</style>

效果如下

可以直接调整影响半径,缓冲区会动态绘制


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