一、电子围栏是什么?

电子围栏 = 地图上画一个多边形区域

核心功能
  • 在地图上手动绘制围栏
  • 获取围栏的所有经纬度坐标
  • 传入一个坐标(如设备 GPS、人员定位)
  • 自动判断该坐标是否在围栏内
  • 支持围栏编辑、回显、保存
适用场景
  • 考勤打卡范围限制
  • 电动车 / 设备电子围栏报警
  • 物流车辆区域限制
  • 智慧工地人员越界告警
  • 巡检范围限定

二、核心技术说明

  • 框架:UniApp
  • 平台:微信小程序
  • 组件:uni-map 地图组件(小程序原生 map)
  • 核心算法:射线法(点是否在多边形内)
  • 能力:地图展示、多点连线、多边形绘制、坐标计算

三、前置准备

1.开启小程序地图权限

微信公众平台 → 开发 → 开发设置

2.配置 manifest.json

在 微信小程序配置 → 权限配置 → 位置接口 里添加描述
在这里插入图片描述

3.使用真机调试

地图、定位在浏览器 / 模拟器定位会出现偏移(非当前定位)!

四、页面代码

<template>
  <!-- 微信小程序地图电子围栏 -->
  <view class="map-container">
    <!-- 地图组件 -->
    <map
      id="myMap"
      ref="mapRef"
      class="map-box"
      :longitude="centerLong"
      :latitude="centerLat"
      :scale="16"
      :polygons="polygons"
      @tap="mapTap"
      show-location
    >
    </map>

    <!-- 工具栏 -->
    <view class="tool-bar">
      <button type="primary" size="mini" @click="startDrawFence">
        {{ isDraw ? '绘制中...' : '开始绘制围栏' }}
      </button>
      <button type="warn" size="mini" @click="clearFence">清空围栏</button>
      <button type="default" size="mini" @click="saveFence">保存围栏</button>
      <button type="primary" size="mini" @click="testPointInFence">
        测试:当前位置是否在围栏内
      </button>
    </view>

    <!-- 结果提示 -->
    <view class="tip" v-if="tipMsg">{{ tipMsg }}</view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      // 地图中心点
      centerLong: 116.39748,
      centerLat: 39.90882,

      // 绘制围栏
      isDraw: false,
      pointList: [], // 围栏点集合

      // 地图多边形
      polygons: [],

      // 提示
      tipMsg: ''
    }
  },
  onReady() {
    this.getMyLocation()
  },
  methods: {
    // 获取当前定位
    getMyLocation() {
      uni.getLocation({
        type: 'gcj02',
        success: res => {
          this.centerLong = res.longitude
          this.centerLat = res.latitude
        }
      })
    },

    // 开始绘制围栏
    startDrawFence() {
      this.isDraw = true
      this.pointList = []
      this.polygons = []
      this.tipMsg = '点击地图绘制围栏点'
    },

    // 点击地图添加点
    mapTap(e) {
      if (!this.isDraw) return

      const { longitude, latitude } = e.detail
      this.pointList.push({
        longitude: Number(longitude),
        latitude: Number(latitude)
      })

      // 至少3个点才构成围栏
      if (this.pointList.length >= 3) {
        this.updatePolygon()
      }
    },

    // 更新地图多边形
    updatePolygon() {
      this.polygons = [
        {
          points: this.pointList,
          strokeWidth: 2,
          strokeColor: '#1890ff',
          fillColor: 'rgba(24,144,255,0.2)'
        }
      ]
    },

    // 清空围栏
    clearFence() {
      this.isDraw = false
      this.pointList = []
      this.polygons = []
      this.tipMsg = '围栏已清空'
    },

    // 保存围栏(可上传后端)
    saveFence() {
      if (this.pointList.length < 3) {
        uni.showToast({ title: '至少3个点', icon: 'none' })
        return
      }

      // 围栏坐标点(传给后端存储)
      const fenceData = {
        points: this.pointList,
        center: {
          longitude: this.centerLong,
          latitude: this.centerLat
        }
      }

      console.log('电子围栏数据:', fenceData)
      uni.setStorageSync('myFence', fenceData)

      this.tipMsg = '围栏保存成功'
      this.isDraw = false
    },

    // 测试:当前位置是否在围栏内
    testPointInFence() {
      if (this.pointList.length < 3) {
        uni.showToast({ title: '请先绘制围栏', icon: 'none' })
        return
      }

      uni.getLocation({
        type: 'gcj02',
        success: res => {
          const point = {
            longitude: res.longitude,
            latitude: res.latitude
          }

          // 核心算法:判断点是否在多边形内
          const isIn = this.isPointInPolygon(point, this.pointList)

          if (isIn) {
            this.tipMsg = '✅ 当前位置在围栏内'
          } else {
            this.tipMsg = '❌ 当前位置不在围栏内'
          }
        }
      })
    },

    // ====================
    // 核心算法:射线法
    // ====================
    isPointInPolygon(point, polygon) {
      let inside = false
      const x = point.longitude
      const y = point.latitude

      for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
        const xi = polygon[i].longitude, yi = polygon[i].latitude
        const xj = polygon[j].longitude, yj = polygon[j].latitude

        const intersect = ((yi > y) != (yj > y))
          && (x < (xj - xi) * (y - yi) / (yj - yi) + xi)

        if (intersect) inside = !inside
      }
      return inside
    }
  }
}
</script>

<style scoped>
.map-container {
  width: 100%;
  height: 100vh;
  position: relative;
}
.map-box {
  width: 100%;
  height: 80vh;
}
.tool-bar {
  padding: 15rpx;
  display: flex;
  flex-wrap: wrap;
  gap: 10rpx;
}
.tip {
  padding: 20rpx;
  text-align: center;
  font-size: 30rpx;
  color: #1890ff;
}
</style>

五、功能模块详解

1.地图组件配置

<map
  :longitude="centerLong"
  :latitude="centerLat"
  :polygons="polygons"
  @tap="mapTap"
  show-location
/>
  • show-location 显示自身定位蓝点
  • polygons 渲染多边形围栏
  • @tap 点击地图添加点

2.绘制围栏逻辑

流程:
1.点击【开始绘制】
2.点击地图添加坐标点
3.≥3 个点自动生成围栏
4.蓝色半透明区域就是围栏

3.绘制围栏逻辑

{
  points: 坐标数组,
  strokeWidth: 边框宽度,
  strokeColor: 边框颜色,
  fillColor: 填充色
}

4.射线法

原理:从点向右发射射线,与多边形边相交次数为奇数 → 在内部

支持:

  • 凸多边形
  • 凹多边形
  • 复杂围栏
  • 小程序 / APP/H5 通用

六、电子围栏常见业务用法

1.后端保存围栏

saveFence() {
  // 直接把 pointList 传给后端
  // 格式:[{latitude,longitude}, ...]
}

2.回显围栏(编辑模式)

onLoad() {
  const fence = uni.getStorageSync('myFence')
  if (fence) {
    this.pointList = fence.points
    this.updatePolygon()
  }
}

3.实时监控是否越界

// 定时获取定位判断是否在围栏内
setInterval(() => {
  uni.getLocation({
    success: res => {
      const inFence = this.isPointInPolygon(res, this.pointList)
      if (!inFence) {
        uni.showModal({ title: '警告', content: '已超出电子围栏' })
      }
    }
  })
}, 5000)

七、常见问题

1.地图不显示、不定位

  • 必须真机调试
  • 必须开启位置权限
  • 必须配置 manifest.json 权限

2.绘制围栏不显示

  • 至少需要 3 个点
  • 坐标必须是 Number 类型

3.判断是否在围栏内不准确

  • 地图必须用 gcj02 坐标系(国测局)
  • uni.getLocation 必须指定 type: ‘gcj02’

4.小程序审核被拒

描述写:
用于考勤/设备管理/区域限制功能,获取位置用于判断是否在电子围栏范围内


总结

本篇完整实现了 UniApp 微信小程序电子围栏功能,包含:

  • 地图绘制多边形围栏
  • 点是否在多边形内核心算法
  • 围栏保存、编辑、回显、清空
  • 越界告警 / 考勤判断全套逻辑

更多推荐