vue实现天地图电子围栏

一、文档

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

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

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

二、分析

所谓电子围栏

1、就是在地图商通过经纬度将点标注出来,然后将这些点连起来渲染为一个面

2、然后在此基础上支持编辑即添加删除以及拖拽

3、在每次操作完成之后都对天地图视图重新渲染

三、代码实现

1、在线体验

2、渲染标注点以及面

文档:添加标注点 添加多边形

直接通过坐标点把标注点以及多边形渲染出来

javascript 复制代码
<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显示出来即可完成

javascript 复制代码
<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>
相关推荐
customer081 分钟前
【开源免费】基于SpringBoot+Vue.JS课程答疑系统(JAVA毕业设计)
java·jvm·vue.js·spring boot·spring cloud·kafka·开源
abments6 分钟前
JavaScript逆向爬虫教程-------基础篇之JavaScript密码学以及CryptoJS各种常用算法的实现
javascript·爬虫·密码学
清灵xmf8 分钟前
TypeScript 中的 ! 和 ? 操作符
前端·javascript·typescript·?·
J总裁的小芒果15 分钟前
el-table中增加校验方法(二)
javascript·vue.js·elementui
Au_ust16 分钟前
css:权重计算
前端·css
葫芦鱼16 分钟前
怎么打造一个舒适的nodejs开发环境
前端·typescript
测试界的酸菜鱼1 小时前
使用 Python + Vue 搭建自动化平台的核心要点
vue.js·python·自动化
林太白1 小时前
手写Vue之Api-createApp()
前端
黑色叉腰丶大魔王1 小时前
《Javascript 网页设计案例分享》
javascript
前端Hardy1 小时前
HTML&CSS 打造的酷炫菜单选项卡
前端·javascript·css·html·css3