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

news2025/1/13 9:24:01

前言

在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)

  //缓冲后的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的写法,用起来很好用

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

效果如下

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


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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1374515.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

上架苹果APP的时候在哪里填写APP的隐私政策信息

在如今高度重视数据隐私的时代&#xff0c;开发并上架一个iOS APP时提供透明的隐私政策是非常重要的。苹果公司对此有严格的规定&#xff0c;任何上架至App Store的应用都必须包含一个隐私政策。以下是您在上架苹果APP时填写隐私政策信息的详细步骤和必须注意的事项。 准备隐私…

考古学家 - 华为OD统一考试

OD统一考试 分值&#xff1a; 200分 题解&#xff1a; Java / Python / C 题目描述 有一个考古学家发现一个石碑&#xff0c;但是很可惜发现时其已经断成多段。 原地发现N个断口整齐的石碑碎片&#xff0c;为了破解石碑内容&#xff0c;考古学家希望有程序能帮忙计算复原后的石…

最新PyCharm安装详细教程及pycharm配置_pycharm安装教程

目录 一、PyCharm简介及其下载网站 二、单击网站的Downloads&#xff0c;进入二级页面&#xff0c;选择对应的操作系统下载PyCharm 三、PyCharm的安装程序的安装及其配置(configuration) 1、运行PyCharm Setup 2、安装位置设置 3、安装选项设置 4、开始菜单中PyCharm快捷方式的…

Unity中向量的点乘、叉乘区别和作用以及经典案例

文章目录 点乘&#xff08;Dot Product&#xff09;叉乘&#xff08;Cross Product&#xff09;向量归一化&#xff08;Normalize&#xff09;其他作用 unity开发中我们要计算角度&#xff0c;判断位置&#xff0c;常用点乘、叉乘、归一化等等&#xff0c;我们看看他们的使用案…

Android 通知简介

Android 通知简介 1. 基本通知 图1: 基本通知详情 小图标 : 必须提供,通过 setSmallIcon( ) 进行设置.应用名称 : 由系统提供.时间戳 : 由系统提供,也可隐藏时间.大图标(可选) : 可选内容(通常仅用于联系人照片,请勿将其用于应用图标),通过setLargeIcon( ) 进行设置.标题 : 可选…

中国社科院大学与美国杜兰大学金融管理硕士项目——熬过寒冬,春日暖阳已不远

在金融领域&#xff0c;寒冬似乎成了无法避免的阶段。然而&#xff0c;对于那些坚守岗位的金融从业者来说&#xff0c;熬过寒冬并非无望。正如冬去春来&#xff0c;只要我们采取明智的策略&#xff0c;迈出坚定的步伐&#xff0c;春日的暖阳已在不远方照耀。社科院与美国杜兰大…

SpringBoot+Hutool实现图片验证码

图片验证码在注册、登录、交易、交互等各类场景中都发挥着巨大作用&#xff0c;能够防止操作者利用机器进行暴力破解、恶意注册、滥用服务、批量化操作和自动发布等行为。 创建一个实体类封装&#xff0c;给前端返回的验证码数据&#xff1a; Data public class ValidateCodeV…

JS栈和堆:数据是如何存储的

JS栈和堆&#xff1a;数据是如何存储的 背景JavaScript 是什么类型的语言JavaScript 的数据类型内存空间栈空间和堆空间再谈闭包 背景 JS有多种数据类型&#xff1a;数字型&#xff0c;字符串型&#xff0c;数组型等&#xff0c;虽然 JavaScript 并不需要直接去管理内存&#…

都2024年了,FP卖家还不知道AB站怎么玩?

自从开始写FP独立站各种运营技巧和黑科技的文章&#xff0c;经常都会有朋友来私V&#xff0c;询问怎么进行AB站跳转。 可能是现在平台对于FP商家的限制越来越多&#xff0c;再加上如今到处都是“内卷”的电商环境&#xff0c;让FP商家生存越来越艰难&#xff0c;今天就着重讲一…

美当局批准现货比特币ETF,BTC不涨反跌?解读22页官方文件,SEC的担忧被完全解决了吗?

美东时间2024年1月10日下午&#xff0c;美SEC官宣批准现货比特币ETF的上市和交易&#xff0c;这是一个里程碑时刻&#xff0c;代表着加密资产类别获得主流采用的最重要一步。 11只获得批准的现货比特币ETF分别来自&#xff1a;BlackRock、Bitwise、Grayscale、Hashdex、Valkyri…

CAN201 计网大题收集

网络性能计算 e.g1 e.g2 木桶效应 e.g3 吞吐量 e.g4 时延 e.g5 时延 e.g5 e.g e.g6 拓展 e.g7 传输层 TCP D 拥塞控制算法 拥塞控制算法_哔哩哔哩_bilibili 慢开始&#xff0c;拥塞避免&#xff0c;快重传&#xff0c;快恢复 物理层 根据我印象好像不太需要学物理层这块…

科技成果鉴定测试的意义在哪?鉴定测试报告重要吗?

科技成果鉴定测试是指通过一系列的评估和验证过程&#xff0c;对科技成果所包含的技术特征、技术优势以及市场应用前景进行客观、准确的评估和证明的过程。科技成果鉴定测试可以对科技项目进行全面、系统的评估&#xff0c;从而找出项目的优势和不足之处&#xff0c;并为项目的…

usb静电防护芯片选择

方案1 USBLC6-2SC6 优缺点 优点&#xff1a;进出使用不同的焊盘&#xff0c;如果没有焊接好信号必定不能通过。有效的避免了虚焊导致故障。 缺点&#xff1a;不能省略&#xff0c;调试时也不能省略。 原理图 参考价格 参考来源 USB切换方案&#xff0c;多电脑共用USB方案…

零基础小白如何自学sql?

学习SQL对于数据分析和处理来说非常重要。SQL是一种强大的工具&#xff0c;可以帮助你与数据库沟通&#xff0c;提取&#xff0c;整理和理解数据。 以下是一些学习SQL的建议&#xff1a; 01 前期&#xff1a;SQL数据库学习 了解SQL的基本概念&#xff1a;首先&#xff0c;你…

求幸存数之和 - 华为OD统一考试

OD统一考试(C卷) 分值: 100分 题解: Java / Python / C++ 题目描述 给一个正整数列nums,一个跳数jump,及幸存数量left。运算过程为:从索引为0的位置开始向后跳,中间跳过 J 个数字,命中索引为 J+1 的数字,该数被敲出,并从该点起跳,以此类推,直到幸存left个数为止。…

Java高级工程师20道面试题、答案及案例

文章目录 Java高级工程师面试题、答案及案例&#xff1a; 问题&#xff1a; 在Java中&#xff0c;如何实现线程安全的单例模式&#xff1f;请写出双重检查锁定&#xff08;Double-Checked Locking&#xff09;的实现方式。 答案与案例&#xff1a; public class Singleton {pri…

电机控制----------龙伯格观测器引入

一、建立龙波格观测器 通过求解A矩阵的特征值来判断&#xff0c;整个系统是否稳定。 二、状态空间方程

ChatGPT可以帮你做什么?

学习 利用ChatGPT学习有很多&#xff0c;比如&#xff1a;语言学习、编程学习、论文学习拆解、推荐学习资源等&#xff0c;使用方法大同小异&#xff0c;这里以语言学习为例。 在开始前先给GPT充分的信息&#xff1a;&#xff08;举例&#xff09; 【角色】充当一名有丰富经验…

003-10-02【Spark官网思维笔记】香积寺旁老松树边马大爷家女儿大红用GPT学习Spark入门知识

003-10-02【Spark官网思维笔记】香积寺旁老松树边马大爷家女儿大红用GPT学习Spark入门知识. Spark 快速入门快速开始使用 Spark Shell 进行交互式分析&#xff1a;独立的应用程序其他 1, 使用 Spark Shell 进行交互式分析1.1 基本1.2 有关Dataset操作的更多信息1.3 缓存 2&…

STL-list的使用简介

目录 ​编辑 一、list的底层实现是带头双向循环链表 二、list的使用 1、4种构造函数&#xff08;与vector类似&#xff09;​编辑 2、迭代器iterator 3、容量&#xff08;capicity&#xff09;操作 4、element access 元素获取 5、增删查改 list modifiers 6、list的迭…