[Vue] Vue3高德地图, 创建多边形电子围栏

1,669 阅读4分钟

目录

  • 1.直接创建多边形(适用于加载)
  • 2.手动绘制多边形
  • 3.电子围栏的上传和下载
  • 4.判断坐标是否在多边形内

1.直接创建 (内容合并到后面)

2.通过鼠标绘制围栏 (内容合并到后面)

3.电子围栏的上传和下载 (内容合并到后面)

4.判断坐标是否在多边形内

  1. 数据库设计
CREATE TABLE `ba_fence` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `fence_data` varchar(500) DEFAULT NULL,
  `create_time` int(11) DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;

  1. 模型
<?php
namespace app\admin\model;
use think\Model;
class Fence extends Model
{
    protected $name = 'fence';
    protected $autoWriteTimestamp = false;
}
  1. 前端
<template>
    <div class="map-container">
      <div id="map" style="height: 400px; width: 50%; margin-left: 200px;"></div>
      <div class="input-card" style="margin-left: 200px;margin-top: 15px;">
        <h4>添加围栏</h4>
        <div>
          <el-button @click="addPolygon" :disabled="polygonClosed">添加围栏</el-button>
          <el-button @click="clearPolygon">重新绘制</el-button>
          <el-button @click="uploadPolygon">上传围栏</el-button>
          <el-button @click="nowAddress">定位当前</el-button>
          <el-button @click="getFenceData">获取围栏</el-button>
          <el-button @click="isInsidePolygon">判断是否在围栏内</el-button>
        </div>
      </div>
    </div>
  </template>
  
  <script>
  import { ref, onMounted } from 'vue'
  import AMapLoader from '@amap/amap-jsapi-loader'
  import {createFence, getFence, isInside} from '/@/api/controllerUrls' 
  import { ElNotification } from 'element-plus'

  export default {
    name: 'MapComponent',
    setup() {
      let marker = null
      const map = ref(null)
      let polygon = null // Variable to store the polygon
      let polyline = null // Variable to store the polyline
      const pathArr = []
      const polygonClosed = ref(false)
  
      const initMap = async () => {
        AMapLoader.load({
          key: 'YOUR_KEY',
          version: '2.0'
        }).then((AMap) => {
          map.value = new AMap.Map('map', {
            viewMode: '3D',
            zoom: 15,
            resizeEnable: true,
            mapStyle: 'amap://styles/normal',
            center: [121.7789, 31.3312],
            showMarker: true
          })
  
          marker = new AMap.Marker({
            position: [121.7789, 31.3312],
            icon: 'https://webapi.amap.com/theme/v1.3/markers/n/mark_bs.png',
            draggable: true
          })
          map.value.add(marker)
          map.value.setFitView()
  
          map.value.on('click', (e) => {
            const lnglat = e.lnglat
            const newPath = [lnglat.lng, lnglat.lat]
  
            if (pathArr.length > 0 && !polygonClosed.value) {
              pathArr.push(newPath)
  
              if (polyline) {
                polyline.setMap(null)
              }
  
              polyline = new AMap.Polyline({
                map: map.value,
                path: pathArr,
                strokeColor: '#3366FF',
                strokeOpacity: 1,
                strokeWeight: 3,
              })
            } else {
              pathArr.push(newPath)
            }
  
             // 判断是否闭合多边形
              const tolerance = 0.01; // 设置一个适当的误差阈值
              if (pathArr.length > 1 && isCloseEnough(pathArr[pathArr.length - 1], pathArr[0], tolerance, map.value)) {
                  pathArr.push(pathArr[0]); // 将第一个点加入到路径末尾,闭合多边形
                  addPolygon(); // 闭合多边形
                  polygonClosed.value = true; // 将状态设置为闭合
              }
  
          })
        }).catch((e) => {
          console.error(e)
        })
      }
  
      // 定义一个函数来判断两个点是否在允许的误差范围内相等
      function isCloseEnough(point1, point2, tolerance, map) {
          const [lng1, lat1] = point1;
          const [lng2, lat2] = point2;
          const distance = Math.sqrt(Math.pow(lng2 - lng1, 2) + Math.pow(lat2 - lat1, 2));
          const zoom = map.getZoom(); // 获取当前地图缩放级别
          const adjustedTolerance = tolerance / Math.pow(2, zoom - 10); // 调整误差容差值
          return distance <= adjustedTolerance;
      }
  
      // 添加多边形
      const addPolygon = () => {
        if (polygon) {
          polygon.setMap(null)
        }
  
        polygon = new AMap.Polygon({
          path: pathArr,
          fillColor: '#ccebc5',
          strokeOpacity: 1,
          fillOpacity: 0.5,
          strokeColor: '#2b8cbe',
          strokeWeight: 1,
          strokeStyle: 'dashed',
          strokeDasharray: [5, 5],
        });
  
        polygon.on('mouseover', () => {
          polygon.setOptions({
            fillOpacity: 0.7,
            fillColor: '#7bccc4',
          });
        });
  
        polygon.on('mouseout', () => {
          polygon.setOptions({
            fillOpacity: 0.5,
            fillColor: '#ccebc5',
          });
        });
  
        map.value.add(polygon);
      };
  
      // 重新绘制
      const clearPolygon = () => {
        if (polygon) {
          polygon.setMap(null)
        }
        if (polyline) {
          polyline.setMap(null)
        }
        pathArr.length = 0; // 清空路径数组
        polygonClosed.value = false; // 将状态设置为未闭合
      }
  
      // 上传围栏
      const uploadPolygon = () => {
        if (polygonClosed.value) {
          console.log('闭合围栏坐标集合:', pathArr);

          const formData = new FormData();
          formData.append('fence', JSON.stringify(pathArr)); // 将 pathArr 存储到 'fence' 中

          createFence(formData).then(res => {
            console.log(res);
          }).catch(err => {
            console.log(err);
          });

        } else {
          console.log('围栏未闭合,请完成多边形并闭合后再上传。');
        }
      }

      // 定位当前地址
      const nowAddress = () => {
        if (map.value) {
          AMap.plugin('AMap.Geolocation', function() {
            const geolocation = new AMap.Geolocation({
              enableHighAccuracy: true,
              timeout: 10000,
              buttonOffset: new AMap.Pixel(10, 20),
              zoomToAccuracy: true,
              buttonPosition: 'RB'
            });
            map.value.addControl(geolocation);
            geolocation.getCurrentPosition();
          });
        }
      };

      // 获取围栏
      const getFenceData = () => {
        getFence(1).then((res)=> {
          const fenceData = JSON.parse(res.data.fence);
          drawFenceOnMap(fenceData);
        })
      }

      // 在地图上绘制围栏(获取围栏后重绘)
      const drawFenceOnMap = (fenceData) => {
        if (map.value && fenceData.length > 0) {
          if (polygon) {
            polygon.setMap(null);
          }

          polygon = new AMap.Polygon({
            path: fenceData,
            fillColor: '#ccebc5',
            strokeOpacity: 1,
            fillOpacity: 0.5,
            strokeColor: '#2b8cbe',
            strokeWeight: 1,
            strokeStyle: 'dashed',
            strokeDasharray: [5, 5],
          });

          polygon.on('mouseover', () => {
            polygon.setOptions({
              fillOpacity: 0.7,
              fillColor: '#7bccc4',
            });
          });

          polygon.on('mouseout', () => {
            polygon.setOptions({
              fillOpacity: 0.5,
              fillColor: '#ccebc5',
            });
          });

          map.value.add(polygon);
          map.value.setFitView(polygon);
        }
      };

      const isInsidePolygon = () => {
          if (polygonClosed.value && marker) {
            const markerPosition = marker.getPosition(); // 获取 marker 的坐标

            const formData = new FormData();
            formData.append('fence', JSON.stringify(pathArr)); // 将 pathArr 存储到 'fence' 中
            formData.append('point', JSON.stringify(markerPosition)); // 将 marker 的坐标存储到 'point' 中

            isInside(formData).then(res => {
              
               if (res.data.isInside == 1) {
                  ElNotification({message: '坐标在多边形内'});
               } else {
                  ElNotification({message: '坐标在多边形外部'});
               }

            }).catch(err => {
              console.log(err);
            });
          } else {
            console.log('围栏未闭合或marker不存在,请完成多边形并闭合后再判断');
          }
      }

      onMounted(() => {
        initMap()
      })
  
      return {
        addPolygon,
        clearPolygon,
        uploadPolygon,
        polygonClosed,
        nowAddress,
        getFenceData,
        isInsidePolygon
      }
    }
  }
  </script>
  
  <style>
  .map-container {
    margin: 10px 0;
  }
  
  #map {
    height: 400px;
    width: 100%;
  }
  </style>
  1. 封装api web\src\api\controllerUrls.ts
// 获取地图数据
export function getMapData() {
    return createAxios({
        url: '/admin/dashboard/getMapData',
        method: 'get',
    })
}


// 添加多边形
export function createFence(form: any) {
    return createAxios({
        url: '/admin/dashboard/createFence',
        method: 'post',
        data: form
    })
}

// 获取多边形
export function getFence(id: any) {
    return createAxios({
        url: '/admin/dashboard/getFence?id='+id,
        method: 'get',
    })
}

// 判断是否在多边形内部
export function isInside(form: any) {
    return createAxios({
        url: '/admin/dashboard/isInside',
        method: 'post',
        data: form
    })
}
  1. 后端接口
<?php

namespace app\admin\controller;

use app\admin\model\Fence;
use app\common\controller\Backend;
use utils\GpsUtils;

class Dashboard extends Backend
{
    public function initialize(): void
    {
        parent::initialize();
    }

    public function index(): void
    {
        $this->success('', [
            'remark' => get_route_remark()
        ]);
    }
    
    // 添加围栏
    public function createFence()
    {
        $data = $this->request->post('fence');
        $dataArr = json_decode($data, true);
        
        $fence = new Fence();
        $fence->fence_data = $data;
        if ($fence->save()) {
            $this->success('添加围栏成功');
        } else {
            $this->error('添加围栏失败');
        }
    }


    // 获取围栏
    public function getFence()
    {
        
        $id = $this->request->get('id');
        if ($id == 1) {
            $lastRecord = Fence::order('id', 'desc')->find();
            $id = $lastRecord ? $lastRecord->id : 1;
        }
        $fence = Fence::find($id);
        $this->success('', ['fence'=> $fence->fence_data]);
    }


    public function isInside()
    {
        $fence = $this->request->post('fence');
        $point = $this->request->post('point');
        $dataArr = json_decode($fence, true);

        $point = str_replace('[', '', $point);
        $point = str_replace(']', '', $point);
        $res = $this->containsPoint($dataArr, $point);
        $this->success('', ['isInside' => (int)$res]);
    }

    // 判断点是否在多边形内
    public  function containsPoint($vertices, $ponits) 
    {
        $ponitsArr = explode(',', $ponits);
        $lat = $ponitsArr[0];
        $lng = $ponitsArr[1]; // 需要将经纬度反转

        $crossings = 0;
        $count = count($vertices);

        for ($i = 0; $i < $count; $i++) {
            $a = $vertices[$i];
            $b = $vertices[($i + 1) % $count];

            if ((($a[1] <= $lng) && ($lng < $b[1])) || (($b[1] <= $lng) && ($lng < $a[1]))) {
                $vt = ($lng - $a[1]) / ($b[1] - $a[1]);
                if ($lat < $a[0] + $vt * ($b[0] - $a[0])) {
                    $crossings++;
                }
            }
        }
        // 奇数次交点:点在多边形内部;偶数次交点:点在多边形外部
        return ($crossings % 2 != 0);
    }
}