vue实现天地图电子围栏

news2024/11/7 18:50:37

一、文档

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 = points
      fenceAssembly.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 = points
      fenceAssembly.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);
      })
      // 如果是手动添加,则立即更新围栏数据并调用 operateTMap
      if (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.coloradmin.cn/o/2235200.html

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

相关文章

QT 实现绘制汽车仪表盘

1.界面实现效果 以下是具体的项目需要用到的效果展示,通常需要使用QPainter类来绘制各种图形和文本,包括一个圆形的仪表盘、刻度、指针和数字。 2.简介 分为以下几个部分,首先设置抗锯齿 painter.setRenderHint(QPainter::Antialiasing)。 QPainter p(this);p.setRender…

【网络】传输层协议TCP(下)

目录 四次挥手状态变化 流量控制 PSH标记位 URG标记位 滑动窗口 快重传 拥塞控制 延迟应答 mtu TCP异常情况 四次挥手状态变化 之前我们讲了四次挥手的具体过程以及为什么要进行四次挥手&#xff0c;下面是四次挥手的状态变化 那么我们下面可以来验证一下CLOSE_WAIT这…

阿里云docker安装禅道记录

docker network ls docker network create -d bridge cl_network sudo docker run --name zentao --restart always -p 9982:80 --networkcl_network -v /data/zentao:/data -e MYSQL_INTERNALtrue -d hub.zentao.net/app/zentao:18.5 升级禅道 推荐用按照此文档升级&a…

迈入国际舞台,AORO M8防爆手机获国际IECEx、欧盟ATEX防爆认证

近日&#xff0c;深圳市遨游通讯设备有限公司&#xff08;以下简称“遨游通讯”&#xff09;旗下5G防爆手机——AORO M8&#xff0c;通过了CSA集团的严格测试和评估&#xff0c;荣获国际IECEx及欧盟ATEX防爆认证证书。2024年11月5日&#xff0c;CSA集团和遨游通讯双方领导在遨游…

Win11家庭版 配置 WSL/Ubuntu+Docker详细步骤

最近换了台工作电脑&#xff0c;Windows系统的&#xff0c;想发挥下显卡的AI算算力&#xff0c;所以准备搞下docker环境&#xff0c;下面开始详细介绍&#xff1a; 1、准备系统 最开始是想安装Windows Docker Desktop的&#xff0c;奈何网络问题&#xff0c;死活不能下载镜像…

apache poi 实现下拉框联动校验

apache poi 提供了 DataValidation​ 接口 让我们可以轻松实现 Excel 下拉框数据局校验。但是下拉框联动校验是无法直接通过 DataValidation ​实现&#xff0c;所以我们可以通过其他方式间接实现。 ‍ 步骤如下&#xff1a; 创建一个隐藏 sheet private static void create…

LabVIEW扫描探针显微镜系统

开发了一套基于LabVIEW软件开发的扫描探针显微镜系统。该系统专为微观尺度材料的热性能测量而设计&#xff0c;特别适用于纳米材料如石墨烯、碳纳米管等的研究。系统通过LabVIEW编程实现高精度的表面形貌和热性能测量&#xff0c;广泛应用于科研和工业领域。 项目背景 随着纳…

【Python】强大的正则表达式工具:re模块详解与应用

强大的正则表达式工具&#xff1a;re模块详解与应用 在编程和数据处理中&#xff0c;字符串的处理是不可避免的一项任务。无论是从文本中提取信息、验证数据格式&#xff0c;还是进行复杂的替换操作&#xff0c;正则表达式&#xff08;Regular Expression&#xff0c;简称Rege…

Redis数据库测试和缓存穿透、雪崩、击穿

Redis数据库测试实验 实验要求 1.新建一张user表&#xff0c;在表内插入10000条数据。 2.①通过jdbc查询这10000条数据&#xff0c;记录查询时间。 ②通过redis查询这10000条数据&#xff0c;记录查询时间。 3.①再次查询这一万条数据&#xff0c;要求根据年龄进行排序&#…

今天要重新认识下注解@RequestBody

在Spring框架中&#xff0c;RequestBody是一个常用的注解&#xff0c;它用于将HTTP请求体中的数据绑定到控制器&#xff08;Controller&#xff09;处理方法的参数上。这个注解通常与RESTful Web服务一起使用&#xff0c;在处理POST或PUT请求时尤为常见&#xff0c;因为这些请求…

在vscode中如何利用git 查看某一个文件的提交记录

在 Visual Studio Code (VSCode) 中&#xff0c;你可以使用内置的 Git 集成来查看某个文件的提交历史。以下是具体步骤&#xff1a; 使用 VSCode 内置 Git 功能 打开项目&#xff1a; 打开你的项目文件夹&#xff0c;确保该项目已经是一个 Git 仓库&#xff08;即项目根目录下…

JavaScript 23种经典设计模式简介

23种JavaScript经典设计模式 JavaScript经典设计模式 通过之前的学习&#xff0c;我们知道设计模式是一种解决代码组织、代码复用和代码可维护性等问题的技术方法。它通过将代码以特定的方式组织起来&#xff0c;使代码结构更加清晰、可读性更高、易于维护和扩展。为了在开发…

LangChain Ollama实战文献检索助手(二)少样本提示FewShotPromptTemplate示例选择器

本期是用样例来提示大模型生成我们想要的答案。即在输入中给定提示的样例&#xff0c;以及提示模板&#xff0c;然后匹配较相关的样例进行文献综述。 创建示例样本FewShotPromptTemplate 这里我用GTP-o1生成了几个回答&#xff0c;作为样本 samples [{"theme": &…

R语言*号标识显著性差异判断组间差异是否具有统计意义

前言 该R代码用于对Iris数据集进行多组比较分析&#xff0c;探讨不同鸢尾花品种在不同测量变量&#xff08;花萼和花瓣长度与宽度&#xff09;上的显著性差异。通过将数据转换为长格式&#xff0c;并利用ANOVA和Tukey检验&#xff0c;代码生成了不同品种间的显著性标记&#x…

AUTOSAR CP NVRAM Manager规范导读

一、NVRAM Manager功能概述 NVRAM Manager是AUTOSAR(AUTomotive Open System ARchitecture)框架中的一个模块,负责管理非易失性随机访问存储器(NVRAM)。它提供了一组服务和API,用于在汽车环境中存储、维护和恢复NV数据。以下是NVRAM Manager的一些关键功能: 数据存储和…

PDF编辑工具Adobe Acrobat DC 2023安装教程(附安装包)

Adobe Acrobat DC 2023 是 Adobe 公司推出的一款功能强大的 PDF 文档处理软件。它不仅支持创建、编辑和签署 PDF 文件&#xff0c;还提供了丰富的工具来管理和优化这些文件。以下是 Acrobat DC 2023 的一些主要特点&#xff1a; 1.PDF 创建与编辑&#xff1a;用户可以直接从多…

Tornado简单使用

Tornado简单使用 1 介绍 Tornado 是一个基于Python的Web服务框架和 异步网络库&#xff0c;它最初由 FriendFeed 开发&#xff0c;后来被 Facebook 收购并开源&#xff0c;通过利用非阻塞网络 I/O, Tornado 可以承载成千上万的活动连接&#xff0c;完美的实现了 长连接、WebS…

基于SpringBoot的城镇保障性住房管理策略

3系统分析 3.1可行性分析 通过对本城镇保障性住房管理系统实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本城镇保障性住房管理系统采用SSM框架&#xff0c;JA…

【万字详解】如何在微信小程序的 Taro 框架中设置静态图片 assets/image 的 Base64 转换上限值

设置方法 mini 中提供了 imageUrlLoaderOption 和 postcss.url 。 其中&#xff1a; config.limit 和 imageUrlLoaderOption.limit 服务于 Taro 的 MiniWebpackModule.js &#xff0c; 值的写法要 &#xff08;&#xff09;KB * 1024。 config.maxSize 服务于 postcss-url 的…

[实战-11] FlinkSql 设置时区对TIMESTAMP和TIMESTAMP_LTZ的影响

table.local-time-zone table.local-time-zoneDataStream-to-Table Conversion&#xff08;拓展知识&#xff09;代码测试flinksql代码执行结果截图1. Asia/Shanghai 结果如下2. UTC结果如下 table.local-time-zone table.local-time-zone可用于设置flinksql的时区。 flink的内…