微信小程序电子围栏实战: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;
}
这个函数生成的坐标点,可以直接用于 polygons 的 points 属性,从而在地图上渲染出一个视觉上的“圆”。至此,我们完成了从概念到可视化表达的第一步。
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 |

404

被折叠的 条评论
为什么被折叠?



