微信小程序电子围栏实战:5分钟搞定圆形区域定位与距离计算(附完整代码)

微信小程序电子围栏实战:5分钟搞定圆形区域定位与距离计算(附完整代码)

最近在做一个社区服务类的小程序,产品经理提了个需求:用户进入某个特定区域(比如一个公园或者一个商圈)时,小程序要能自动识别并推送欢迎信息。听起来简单,但真动手做,发现里面坑不少。定位精度飘忽不定、地图API调用复杂、判断“在区域内”的算法怎么写才高效……这些问题让不少有基础但没接触过地图开发的同行头疼。

这篇文章,我就把自己趟过的路、踩过的坑,以及最终跑通的完整方案,毫无保留地分享出来。我们的目标很明确:用最短的时间,在小程序里实现一个稳定、准确的圆形电子围栏系统。不仅告诉你代码怎么写,更会解释背后的原理和优化思路,让你知其然,也知其所以然。无论你是想实现签到打卡、区域营销,还是资产安全监控,这套思路都能直接复用。

1. 环境准备与核心思路拆解

在开始敲代码之前,花几分钟理清核心思路和准备工作,能避免后续80%的返工。小程序地图功能主要依赖微信提供的 map 组件和相关的 JavaScript API,而电子围栏的核心,无非是三个步骤:获取用户位置在地图上绘制围栏区域实时计算并判断位置关系

首先,你需要确保小程序已经获得了相应的权限。在 app.json 文件中,声明对用户位置的访问权限是第一步:

{
  "permission": {
    "scope.userLocation": {
      "desc": "您的位置信息将用于判断是否进入服务区域"
    }
  },
  "requiredPrivateInfos": ["getLocation"]
}

注意:从基础库2.24.4版本开始,getLocation接口需要显式在requiredPrivateInfos中声明。务必在真机上测试权限弹窗是否正常触发。

接下来,我们思考一个关键问题:如何表示一个“圆形围栏”?在地理信息系统中,一个圆形区域通常由中心点经纬度半径(米) 来定义。但小程序地图的 polygons(多边形)属性并不直接支持画圆,它需要一组首尾相连的、构成多边形轮廓的坐标点。因此,我们的第一个技术点就变成了:如何用一组多边形顶点来无限逼近一个圆形

这里涉及到一个地理计算公式:根据圆心、半径和等分数,计算出圆边界上的一系列点的经纬度。这个计算过程需要考虑地球的曲率,不能简单用平面几何。一个经过验证的可靠函数如下:

/**
 * 根据圆心和半径,生成近似圆的多边形顶点坐标
 * @param {number} lng - 圆心经度
 * @param {number} lat - 圆心纬度
 * @param {number} radius - 半径(米)
 * @param {number} pointsCount - 生成点的数量(越多越圆,建议60+)
 * @returns {Array} 多边形顶点坐标数组,格式 [{latitude, longitude}, ...]
 */
function generateCirclePoints(lng, lat, radius, pointsCount = 72) {
  const points = [];
  const earthRadius = 6378137; // 地球赤道半径,单位米
  const deltaLng = (radius / (earthRadius * Math.cos(lat * Math.PI / 180))) * (180 / Math.PI);
  const deltaLat = (radius / earthRadius) * (180 / Math.PI);

  for (let i = 0; i < pointsCount; i++) {
    const theta = (i / pointsCount) * 2 * Math.PI;
    // 使用更精确的球面三角函数计算
    const pointLat = lat + (deltaLat * Math.sin(theta));
    const pointLng = lng + (deltaLng * Math.cos(theta));
    points.push({
      latitude: pointLat,
      longitude: pointLng
    });
  }
  // 闭合多边形
  points.push(points[0]);
  return points;
}

这个函数生成的坐标点,可以直接用于 polygonspoints 属性,从而在地图上渲染出一个视觉上的“圆”。至此,我们完成了从概念到可视化表达的第一步。

2. 获取与处理用户定位数据

获取用户位置是小程序地图功能的基础,但这里面的细节直接决定了后续计算的准确性。微信提供了 wx.getLocation 方法,但不同参数获取到的数据精度和类型天差地别。

我强烈建议在开发阶段就统一使用高精度模式,并获取 gcj02 坐标系(国测局坐标系)的坐标。这是腾讯地图和小程序地图组件内部使用的坐标系,保持一致能避免后续大量的坐标转换麻烦。

// pages/index/index.js
Page({
  data: {
    userLocation: null,
    locationError: null
  },

  onLoad() {
    this.getUserLocation();
  },

  getUserLocation() {
    const that = this;
    wx.getLocation({
      type: 'gcj02', // 必须使用gcj02坐标系
      altitude: true, // 获取高度信息,某些场景有用
      isHighAccuracy: true, // 开启高精度
      highAccuracyExpireTime: 3000, // 高精度定位超时时间
      success(res) {
        const { latitude, longitude, speed, accuracy, altitude } = res;
        // accuracy字段表示水平精度半径,单位米。这个值很重要!
        console.log(`定位成功:纬度 ${latitude}, 经度 ${longitude}, 精度半径 ${accuracy}米`);
        that.setData({
          userLocation: { latitude, longitude, accuracy },
          locationError: null
        });
        // 获取到位置后,触发围栏判断
        that.checkFenceStatus();
      },
      fail(err) {
        console.error('定位失败:', err);
        let errMsg = '获取位置失败';
        if (err.errMsg.includes('auth deny')) {
          errMsg = '位置权限未开启,请在设置中授权';
        } else if (err.errMsg.includes('timeout')) {
          errMsg = '定位超时,请检查网络或GPS信号';
        }
        that.setData({ locationError: errMsg });
        wx.showToast({ title: errMsg, icon: 'none' });
      }
    });
  }
})

提示:accuracy 这个参数经常被忽略,但它至关重要。它代表了定位结果的误差范围(一个半径值)。例如,accuracy 为50米,意味着用户的真实位置有68%的概率在以返回坐标为中心、50米为半径的圆内。在判断围栏内外时,尤其是用户靠近围栏边界时,必须考虑这个误差,否则会出现频繁的“闪烁”判断(一会儿在内,一会儿在外)。

处理定位数据时,另一个常见问题是坐标漂移。在室内或信号不佳时,连续获取的位置可能跳跃很大。一个简单的优化策略是加入“平滑处理”:记录最近几次的定位结果,取平均值或中位数作为当前有效位置。这能显著提升用户体验。

// 简单的移动平均平滑处理
const locationHistory = [];
const MAX_HISTORY = 5;

function smoothLocation(newLocation) {
  locationHistory.push(newLocation);
  if (locationHistory.length > MAX_HISTORY) {
    locationHistory.shift();
  }
  
  let sumLat = 0, sumLng = 0;
  locationHistory.forEach(loc => {
    sumLat += loc.latitude;
    sumLng += loc.longitude;
  });
  
  return {
    latitude: sumLat / locationHistory.length,
    longitude: sumLng / locationHistory.length,
    accuracy: newLocation.accuracy // 精度取最新值
  };
}

3. 核心算法:距离计算与围栏内外判断

这是电子围栏功能最核心的部分。我们需要两个算法:1. 计算两点间地表距离;2. 判断一个点是否在多边形(我们的近似圆)内部。

3.1 精确计算两点间距离

计算地球表面两点距离,不能直接用勾股定理,因为地球是球体。最常用的是Haversine公式,它计算的是球面上两点间的大圆距离,精度足够满足大多数LBS应用。

/**
 * 使用Haversine公式计算两个经纬度坐标间的距离(米)
 * @param {number} lat1 - 点1纬度
 * @param {number} lng1 - 点1经度
 * @param {number} lat2 - 点2纬度
 * @param {number} lng2 - 点2经度
 * @returns {number} 距离,单位米
 */
function getDistanceBetweenPoints(lat1, lng1, lat2, lng2) {
  const toRadians = (degree) => degree * Math.PI / 180;
  const R = 6378137; // 地球平均半径,单位米
  
  const dLat = toRadians(lat2 - lat1);
  const dLng = toRadians(lng2 - lng1);
  const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
            Math.cos(toRadians(lat1)) * Math.cos(toRadians(lat2)) *
            Math.sin(dLng / 2) * Math.sin(dLng / 2);
  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  const distance = R * c;
  
  return Math.round(distance * 100) / 100; // 保留两位小数
}

为了让你对不同距离有个直观感受,这里有个简单的对照表:

距离(米) 大致相当于
5
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值