在GIS开发中,缓冲区的绘制和使用是非常广泛的,一般情况下就是对缓冲区范围内的要素做分析使用,也会有一些其他的操作,下面我就记录一下使用leaflet+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)最后得到缓冲后的数据,并将它加入一些样式后添加到地图上显示。
直接上代码,按上面的操作流程和代码注释很容易理解
/**
* @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)
//缓冲后的feature
let 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 = []
//添加绘制line
drawLine = L.polyline(drawPath, { color: 'red' }).addTo(mainMap)
})
mainMap.on('mousemove', (e) => {
if (isDragging) {
let { lat, lng } = e.latlng
drawPath = [...drawPath, [lat, lng]]
drawLine?.setLatLngs(drawPath)
}
mouseEventPopup?.setLatLng(e.latlng)?.setContent(isDragging ? '鼠标抬起完成绘制' : '鼠标按压拖拽绘制')
//如果还没有添加就直接先添加一下
if (!mainMap.hasLayer(mouseEventPopup)) {
//打开方向的popup
mouseEventPopup?.openOn(mainMap)
}
})
//松开鼠标结束拖拽(绘制结束)
mainMap.on('mouseup', (e) => {
status.value = 'end'
isDragging = false
// mainMap.off('mousemove')
mainMap?.removeLayer(drawLine)
//通过绘制的polyline获取缓冲的feature
refreshBuffer()
})
}
//移除事件
let removeEvents = () => {
//按下鼠标
mainMap?.off('mousedown')
//抬起鼠标
mainMap?.off('mouseup')
//结束拖拽事件
mainMap?.off('mousemove')
}
//开始绘制
let startDraw = (type = geometryType) => {
//禁止拖动地图
mainMap?.dragging?.disable()
//初始化事件
initEvents()
}
//清除原来绘制的内容
let clearDrawLayer = () => {
drawLayer?.clearLayers()
}
//添加要素到drawLayer
let 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()
//移除popup
mainMap?.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) {
//生成缓冲后的geoJSON
const buffered = turf.buffer(sourceGeoJSON, distance, {
units,
})
if (buffered) {
return L.geoJSON(buffered, {
style: function(feature) {
return bufferStyle
},
})
}
}
return null
}
代码结构是我学习vue3的自定义hook的写法,用起来很好用
代码如下
<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) return
ruleFormRef.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 = close
startDraw()
}
//监听缓冲半径的变化,进行缓冲区刷新
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>
可以直接调整影响半径,缓冲区会动态绘制
本文为学习笔记,仅供参考