vue实现天地图电子围栏

一、文档

vue3
javascript WGS84、GCj02相互转换
天地图官方文档

注册登录然后申请应用key,通过CDN引入

<script src="http://api.tianditu.gov.cn/api?v=4.0&tk=您的密钥" type="text/javascript"></script>

二、分析

所谓电子围栏
1、就是在地图商通过经纬度将点标注出来,然后将这些点连起来渲染为一个面
2、然后在此基础上支持编辑即添加删除以及拖拽
3、在每次操作完成之后都对天地图视图重新渲染

三、代码实现

1、在线体验

2、渲染标注点以及面

文档:添加标注点 添加多边形
直接通过坐标点把标注点以及多边形渲染出来

<script setup>import { onMounted, ref } from 'vue';defineOptions({name: ''});const props = defineProps({})const emits = defineEmits(['on-ok']);onMounted(() => {setTimeout(() => {initTMap();}, 1000);});const TMap = ref(null);const primaryColor = ref('#0249f9');const latlngPointSet = ref('30.065455529211373,120.56547777078745;30.05799538979555,120.56624760591018;30.06226195624971,120.58499353379405'); // 围栏坐标点集const addMarkerPos = ref({}); // 当前要手动添加的morKer的坐标位置const fenceAssembly = ref({TMapMarker: [],latlngArr: [],latlngStr: '',polygon: null,})function initTMap() {// 转换坐标const latlngArr = latlngPointSet.value.split(';').map(ele => {const [lat, lng] = ele.split(',');return `${lat},${lng}`});fenceAssembly.value = {TMapMarker: [],polygon: null,latlngArr: latlngArr,latlngStr: latlngArr.join(';'),};// 转换坐标(中心点)const center = new T.LngLat(120.57927905745419, 30.05445160751536);// 初始化地图TMap.value = new T.Map('container');TMap.value.centerAndZoom(center, 15);operateTMap();}// 渲染天地图function operateTMap() {if (!TMap.value) return;// 先清除所有覆盖物TMap.value.clearOverLays();const data = fenceAssembly.value.latlngArr || [];// 添加标注点data.forEach(ele => {const [lat, lng] = ele.split(',');addMarkerPos.value = new T.LngLat(lng, lat)addMarker();})// 创建并添加多边形对象const points = data.map(ele => {const [lat, lng] = ele.split(',');return new T.LngLat(lng, lat);});if (points.length >= 3) { // 确保有足够的点来创建一个多边形fenceAssembly.value.points = pointsfenceAssembly.value.polygon = new T.Polygon(points, {color: primaryColor.value,weight: 3,opacity: 1,fillColor: "#44aaf8",fillOpacity: 0.3});TMap.value.addOverLay(fenceAssembly.value.polygon);}}// 添加标注点function addMarker() {const latLng = addMarkerPos.value;if (!TMap.value || !latLng) return;// 获取当前标记的数量(下标)const markerIndex = fenceAssembly.value.TMapMarker ? fenceAssembly.value.TMapMarker.length : 0;// 生成文本图像(假设这是一个异步操作)generateTextImage(markerIndex + 1, (url) => {const icon = new T.Icon({iconUrl: url,iconAnchor: new T.Point(10, 10)});// 创建标记const marker = new T.Marker(latLng, {icon: icon});fenceAssembly.value.TMapMarker.push(marker); // 直接推入数组,使用数组长度作为索引// 将标记添加到地图上TMap.value.addOverLay(marker);});}// 生成带序号的图片function generateTextImage(text, callback) {// 创建Canvas元素var canvas = document.createElement('canvas');var ctx = canvas.getContext('2d');// 设置Canvas大小canvas.width = 20;canvas.height = 20;ctx.beginPath();ctx.arc(10, 10, 10, 0, 2 * Math.PI);// 绘制背景ctx.fillStyle = primaryColor.value;ctx.fill();// 绘制文字ctx.fillStyle = '#ffffff';ctx.font = '15px Arial';ctx.textAlign = 'center';ctx.textBaseline = 'middle';ctx.fillText(text || 1, canvas.width / 2, canvas.height / 1.8);// 将Canvas转换为DataURL格式的图片var dataURL = canvas.toDataURL('image/png');callback(dataURL)}// 子组件暴露defineExpose({});</script><template><div id="container" style="height: 100%;width: 100%;"></div>
</template><style lang="less" scoped></style>

在这里插入图片描述

3、添加点、删除点、拖拽点

文档
添加:鼠标左键点击地图直接点击添加、鼠标右键弹出二级菜单手动添加,直接在初始化时,通过监听地图 点击/右键菜单 事件,获取到点击点对应的经纬度坐标,而后直接执行添加标注点的方法即可
删除:仅可在鼠标右键标注点的时候通过二级菜单进行,故此直接监听标注点右键事件即可
拖拽:直接监听点的推拽事件 代码示例

右键菜单的实现:直接定义html节点,css设置定位、display为none,监听鼠标右键事件,触发后获取到基于视图窗口的xy坐标,而后display设置为block显示出来即可完成

<script setup>import { onMounted, ref } from 'vue';defineOptions({name: ''});const props = defineProps({})const emits = defineEmits(['on-ok']);onMounted(() => {setTimeout(() => {initTMap();}, 1000);});const TMap = ref(null);const primaryColor = ref('#0249f9');const latlngPointSet = ref('30.065455529211373,120.56547777078745;30.05799538979555,120.56624760591018;30.06226195624971,120.58499353379405'); // 围栏坐标点集const addMarkerPos = ref({}); // 当前要手动添加的morKer的坐标位置const delMarkerIndex = ref(-1); // 当前删除的点下标const fenceAssembly = ref({TMapMarker: [],latlngArr: [],latlngStr: '',polygon: null,})function initTMap() {// 转换坐标const latlngArr = latlngPointSet.value.split(';').map(ele => {const [lat, lng] = ele.split(',');return `${lat},${lng}`});fenceAssembly.value = {TMapMarker: [],polygon: null,latlngArr: latlngArr,latlngStr: latlngArr.join(';'),};// 转换坐标(中心点)const center = new T.LngLat(120.57927905745419, 30.05445160751536);// 初始化地图TMap.value = new T.Map('container');TMap.value.centerAndZoom(center, 15);setupEditMode();operateTMap();}// 编辑模式专用设置function setupEditMode() {// 定义一个辅助函数来检查并处理添加标记点的逻辑const checkAndAddMarker = (event, liJiZX = false) => {if (fenceAssembly.value.latlngArr?.length >= 20) {ElMessage({message: '电子围栏最多允许添加20个坐标点!',type: 'warning',})return;}addMarkerPos.value = event.lnglat;if (liJiZX) {addMarker(true);contextmenuFn(''); // 隐藏右键菜单} else {contextmenuFn('map', event.containerPoint.x, event.containerPoint.y); // 打开右键菜单}};// 监听地图点击事件TMap.value.addEventListener('click', (event) => {checkAndAddMarker(event, true);});// 监听地图右键菜单事件TMap.value.addEventListener('contextmenu', (event) => {checkAndAddMarker(event);});}// 渲染天地图function operateTMap() {if (!TMap.value) return;// 先清除所有覆盖物TMap.value.clearOverLays();// 将顶点渲染为图像标注在地图上const data = fenceAssembly.value.latlngArr || [];// 先移除监听if ((fenceAssembly.value.TMapMarker || []).length > 0) {fenceAssembly.value.TMapMarker.forEach(ele => {ele.removeEventListener('dragend');ele.removeEventListener('contextmenu');})}fenceAssembly.value.TMapMarker = [];data.forEach(ele => {const [lat, lng] = ele.split(',');addMarkerPos.value = new T.LngLat(lng, lat)addMarker();})// 创建并添加多边形对象const points = data.map(ele => {const [lat, lng] = ele.split(',');return new T.LngLat(lng, lat);});if (points.length >= 3) { // 确保有足够的点来创建一个多边形fenceAssembly.value.points = pointsfenceAssembly.value.polygon = new T.Polygon(points, {color: primaryColor.value,weight: 3,opacity: 1,fillColor: "#44aaf8",fillOpacity: 0.3});TMap.value.addOverLay(fenceAssembly.value.polygon);// fenceAssembly.value.polygon.enableEdit();}}// 添加标注点function addMarker(isManualAdd = false) {const latLng = addMarkerPos.value;if (!TMap.value || !latLng) return;// 获取当前标记的数量(下标)const markerIndex = fenceAssembly.value.TMapMarker ? fenceAssembly.value.TMapMarker.length : 0;// 生成文本图像(假设这是一个异步操作)generateTextImage(markerIndex + 1, (url) => {const icon = new T.Icon({iconUrl: url,iconAnchor: new T.Point(10, 10)});// 创建标记const marker = new T.Marker(latLng, {icon: icon});fenceAssembly.value.TMapMarker.push(marker); // 直接推入数组,使用数组长度作为索引// 将标记添加到地图上TMap.value.addOverLay(marker);// 开启拖拽marker.enableDragging();// 监听拖拽完成事件marker.addEventListener('dragend', (event) => {const {lat, lng} = event.lnglat;const latlngStr = `${lat},${lng}`;fenceAssembly.value.latlngArr[markerIndex] = latlngStr; // 使用正确的索引更新数据fenceAssembly.value.latlngStr = fenceAssembly.value.latlngArr.join(';');operateTMap(); // 重新渲染地图以反映更改});// 监听右键菜单事件marker.addEventListener('contextmenu', (event) => {delMarkerIndex.value = markerIndex;contextmenuFn('marKer', event.containerPoint.x, event.containerPoint.y);})// 如果是手动添加,则立即更新围栏数据并调用 operateTMapif (isManualAdd) {const {lat, lng} = latLng;const latlngStr = `${lat},${lng}`;fenceAssembly.value.latlngArr.push(latlngStr); // 如果是手动添加,可能需要推入新元素fenceAssembly.value.latlngStr = fenceAssembly.value.latlngArr.join(';');contextmenuFn('');operateTMap();}});}// 手动删除点function delMarkerFn() {// 删除指定下标的点fenceAssembly.value.latlngArr.splice(delMarkerIndex.value, 1);fenceAssembly.value.latlngStr = fenceAssembly.value.latlngArr.join(';');// 右键菜单隐藏contextmenuFn('');// 重新渲染天地图operateTMap();}// 生成带序号的图片function generateTextImage(text, callback) {// 创建Canvas元素var canvas = document.createElement('canvas');var ctx = canvas.getContext('2d');// 设置Canvas大小canvas.width = 20;canvas.height = 20;ctx.beginPath();ctx.arc(10, 10, 10, 0, 2 * Math.PI);// 绘制背景ctx.fillStyle = primaryColor.value;ctx.fill();// 绘制文字ctx.fillStyle = '#ffffff';ctx.font = '15px Arial';ctx.textAlign = 'center';ctx.textBaseline = 'middle';ctx.fillText(text || 1, canvas.width / 2, canvas.height / 1.8);// 将Canvas转换为DataURL格式的图片var dataURL = canvas.toDataURL('image/png');callback(dataURL)}// 右键菜单操作function contextmenuFn(mode = '', left = null, top = null) {if (mode && left && top) {// 打开指定菜单const contextmenu = document.getElementById('contextmenu_' + mode);contextmenu.style.display = 'block';contextmenu.style.left = left + 'px';contextmenu.style.top = top + 'px';}// 隐层其他右键菜单['map', 'marKer'].filter(item => item !== mode).forEach(item => {const contextmenu = document.getElementById('contextmenu_' + item);contextmenu.style.display = 'none';});}// 子组件暴露defineExpose({});</script><template><div id="mainMap"><div id="container" style="height: 100%;width: 100%;"></div><!-- 地图右键菜单 --><div id="contextmenu_map" class="contextmenu_map"><div class="contextmenu_map_item" @click="addMarker(true)">添加坐标点</div></div><!-- marKer右键菜单 --><div id="contextmenu_marKer" class="contextmenu_marKer"><div class="contextmenu_marKer_item" @click="delMarkerFn">删除</div></div></div>
</template><style lang="less" scoped>
#mainMap {width: 100%;height: 100%;background-color: #f1f1f1;color: #000;position: relative;.tips {position: absolute;right: 10px;z-index: 2000;top: 10px;}.save {position: absolute;left: 10px;z-index: 2000;top: 10px;}.fenceItem {// position: relative;margin-bottom: 10px;.h1Title {border-bottom: none;padding-bottom: 0;margin-bottom: 0;}.butBox {display: flex;}}.mainRight {position: relative;.but {position: absolute;top: 10px;left: 10px;z-index: 400;}}.contextmenu_map,.contextmenu_polygon,.contextmenu_marKer {display: none;position: absolute;z-index: 1000;background-color: #f9f9f9;border: 1px solid #ccc;border-radius: 4px;padding: 5px 0;box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);&_item {padding: 10px 20px;cursor: pointer;}&_item:hover {background-color: #f1f1f1;}}
}
</style>

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

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

相关文章

【C++前缀和 单调栈】1124. 表现良好的最长时间段|1908

本文涉及的基础知识点 C算法&#xff1a;前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频 C单调栈 LeetCode 1124. 表现良好的最长时间段 给你一份工作时间表 hours&#xff0c;上面记录着某一位员工每天的工作小时数。 我们认为当员工一天中的工作小时数大…

qt5将程序打包并使用

一、封装程序 (1)、点击创建项目->库->clibrary &#xff08;2&#xff09;、填写自己想要封装成库的名称&#xff0c;这里我填写的名称为mydll1 &#xff08;3&#xff09;、如果没有特殊的要求&#xff0c;则一路下一步&#xff0c;最终会出现如下文件列表。 (4)、删…

PICO+Unity MR空间锚点

官方链接&#xff1a;空间锚点 | PICO 开发者平台 注意&#xff1a;该功能只能打包成APK在PICO 4 Ultra上真机运行&#xff0c;无法通过串流或PICO developer center在PC上运行。使用之前要开启视频透视。 在 Inspector 窗口中的 PXR_Manager (Script) 面板上&#xff0c;勾选…

网页中的某个元素高度突然无法设置

做网页时本来一个div的高度好好的&#xff0c;结果代码打着打着突然发现有个div的高度变的很小&#xff0c;把我很多在这个div里的元素给搞的看不见了。 找了好久的原因最后发现是这个div的结束标签</div>不小心被我删了,之后把这个</div>给补上就好了。

17、论文阅读:VMamba:视觉状态空间模型

前言 设计计算效率高的网络架构在计算机视觉领域仍然是一个持续的需求。在本文中&#xff0c;我们将一种状态空间语言模型 Mamba 移植到 VMamba 中&#xff0c;构建出一个具有线性时间复杂度的视觉主干网络。VMamba 的核心是一组视觉状态空间 (VSS) 块&#xff0c;搭配 2D 选择…

ENSP (虚拟路由冗余协议)VRRP配置

VRRP&#xff08;Virtual Router Redundancy Protocol&#xff0c;虚拟路由冗余协议&#xff09;是一种用于提高网络可用性和可靠性的协议。它通过在多个路由器之间共享一个虚拟IP地址&#xff0c;确保即使一台路由器发生故障&#xff0c;网络依然能够正常运行&#xff0c;防止…

【Leecode】Leecode刷题之路第44天之通配符匹配

题目出处 44-通配符匹配-题目出处 题目描述 个人解法 思路&#xff1a; todo代码示例&#xff1a;&#xff08;Java&#xff09; todo复杂度分析 todo官方解法 44-通配符匹配-官方解法 前言 本题与10. 正则表达式匹配非常类似&#xff0c;但相比较而言&#xff0c;本题稍…

Redis - 主从复制

在分布式系统中为了解决单点问题&#xff0c;通常会把数据复制多个副本部署到其他服务器&#xff0c;满⾜故障恢 复和负载均衡等需求。Redis也是如此&#xff0c;它为我们提供了复制的功能&#xff0c;实现了相同数据的多个Redis副 本。复制功能是⾼可⽤Redis的基础&#xff0c…

推荐一款高级的安装程序打包工具:Advanced Installer Architect

AdvanCEd Installer Architect是一款高级的安装程序打包工具&#xff0c;我们有时候可能用nsis用的多&#xff0c;Advanced Installer Architect也是一款打包工具&#xff0c;有兴趣的朋友也可以试试。有了Advanced Installer Architect你就可以创建MSI打包。 主要功能 *先进的…

【信号处理】基于联合图像表示的深度学习卷积神经网络

Combined Signal Representations for Modulation Classification Using Deep Learning: Ambiguity Function, Constellation Diagram, and Eye Diagram 信号表示 Ambiguity Function(AF) 模糊函数描述了信号的两个维度(dimensions):延迟(delay)和多普勒(Doppler)。 …

CocosCreator 构建透明背景应用(最新版!!!)

文章目录 透明原理补充设置截图以及代码step1: electron-js mian.jsstep2:ENABLE_TRANSPARENT_CANVASstep3:SOLID_COLOR Transparentstep:4 Build Web phonestep5:package electron-js & change body background-color 效果图补充 透明原理 使用Cocos creator 做桌面应用开…

大数据学习11之Hive优化篇

1.Hive压缩 1.1概述 当前的大数据环境下&#xff0c;机器性能好&#xff0c;节点更多&#xff0c;但并不代表我们无条件直接对数据进行处理&#xff0c;在某些情况下&#xff0c;我们依旧需要对数据进行压缩处理&#xff0c;压缩处理能有效减少存储系统的字节读取数&#xff0…

C++builder中的人工智能(13):SELU激活函数在C++应用中的工作原理

SELU&#xff08;Scaled Exponential Linear Unit&#xff09;激活函数是一种在人工神经网络&#xff08;ANN&#xff09;中使用的高级激活函数。它是由Gnter Klambauer, Thomas Unterthiner, Andreas Mayr在2017年提出的&#xff0c;旨在创建自归一化的神经网络&#xff08;Se…

运用Agent搭建“狼人杀”游戏服务器端!

背景 从23年开年以来&#xff0c;大模型引爆了各行各业。去年比较出圈的是各类文生图的应用&#xff0c;比如Stable Diffusion。网上可以看到各类解释其背后的原理和应用的文章。另外一条平行线&#xff0c;则是文生文的场景。受限于当时LLM&#xff08;大语言模型&#xff09…

性能调优专题(6)之MVCC多版本并发控制

一、概述 Mysql在可重复读隔离级别下如果保证事务较高的隔离性,在上一个篇章有详细介绍,同样的sql语句在一个事务多次执行查询结果相同,就算其他事务对数据进行修改也不会影响到当前事务sql语句的查询结果。 这个隔离性就是靠MVCC(Multi-Version Concurrency Control)机制来…

ArcGIS/QGIS按掩膜提取或栅格裁剪后栅格数据的值为什么变了?

问题描述&#xff1a; 现有一栅格数据&#xff0c;使用ArcGIS或者QGIS按照矢量边界进行按掩膜提取或者栅格裁剪以后&#xff0c;其值的范围发生了变化&#xff0c;如下&#xff1a; 可以看到&#xff0c;不论是按掩膜提取还是进行栅格裁剪后&#xff0c;其值的范围均与原来栅…

后台管理系统窗体程序:文章管理 > 文章列表

目录 文章列表的的功能介绍&#xff1a; 1、进入页面 2、页面内的各种功能设计 &#xff08;1&#xff09;文章表格 &#xff08;2&#xff09;删除按钮 &#xff08;3&#xff09;编辑按钮 &#xff08;4&#xff09;发表文章按钮 &#xff08;5&#xff09;所有分类下拉框 &a…

大数据学习10之Hive高级

1.Hive高级 将大的文件按照某一列属性进行GROUP BY 就是分区&#xff0c;只是默认开窗存储&#xff1b; 分区是按行&#xff0c;如一百行数据&#xff0c;按十位上的数字分区&#xff0c;则有十个分区&#xff0c;每个分区里有十行&#xff1b; 分桶是根据某个字段哈希对桶数取…

Me-LLaMA——用于医疗领域的新型开源大规模语言模型

摘要 大规模语言模型的出现是提高病人护理质量和临床操作效率的一个重大突破。大规模语言模型拥有数百亿个参数&#xff0c;通过海量文本数据训练而成&#xff0c;能够生成类似人类的反应并执行复杂的任务。这在改进临床文档、提高诊断准确性和管理病人护理方面显示出巨大的潜…

练习LabVIEW第四十四题

学习目标&#xff1a; 计算学生三门课(语文&#xff0c;数学&#xff0c;英语)的平均分&#xff0c;并根据平均分划分成绩等级。要求输出等级A,B,C,D,E。90分以上为A&#xff0c;80&#xff5e;89为B&#xff0c;70&#xff5e;79为C&#xff0c;60&#xff5e;69为D&#xff…