在微信公众号里做LBS功能时,地理位置上报是最常见的需求之一。如果不管用户是坐着不动还是在高速上开车,都用同一个频率去调微信JS-SDK的getLocation,要么浪费流量和电量,要么轨迹断得没法看。合理的做法是把移动速度算出来,用速度决定多久报一次。下面从原理、前端实现和后台策略三个层面拆开讲。

速度估算与分桶的基本原理
微信的wx.getLocation返回的是经纬度,本身不带速度字段(除非用type: 'gcj02'配合特定设备,也并不稳定)。所以我们只能在前端用两次定位之间的距离差,除以时间差,得到平均移动速度。地球表面两点距离可以用半正矢公式(Haversine)算,拿到米数之后除以秒数,就是米每秒。
拿到速度以后,不要搞连续变量去动态算间隔,那样定时器不好管理。更实用的办法是速度分桶:0到1米每秒算静止,1到3米每秒算步行,3到10米每秒算骑行,10以上算驾车。每个桶对应一个上报周期,比如静止30秒、步行15秒、骑行8秒、驾车5秒。这样逻辑清晰,也方便后续在后台做统计。
需要注意,城市里GPS漂移会让算出来的速度忽高忽低。所以速度不能只看最后一次,而要取最近三到五次定位的滑动平均,并且设定一个最小位移阈值,比如小于3米就不更新速度桶,避免用户在路灯下被误判成骑车。
前端按速度切换上报频率的代码实现
核心思路是用一个递归的setTimeout代替setInterval,每次上报完根据当前速度桶计算下一次的等待时间。下面这段代码演示了如何估算速度并动态调度。
// 简化示例:微信公众号内按速度调整上报频率
var lastPoint = null;
var lastTime = 0;
var speedBucket = 'still'; // still, walk, ride, drive
function haversine(p1, p2) {
var R = 6371000;
var rad = Math.PI / 180;
var dLat = (p2.lat - p1.lat) * rad;
var dLng = (p2.lng - p1.lng) * rad;
var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(p1.lat * rad) * Math.cos(p2.lat * rad) *
Math.sin(dLng / 2) * Math.sin(dLng / 2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c;
}
function getBucket(speed) {
if (speed < 1) return 'still';
if (speed < 3) return 'walk';
if (speed < 10) return 'ride';
return 'drive';
}
function intervalByBucket(bucket) {
return { still: 30000, walk: 15000, ride: 8000, drive: 5000 }[bucket];
}
function reportLocation() {
wx.getLocation({
type: 'gcj02',
success: function(res) {
var now = Date.now();
if (lastPoint) {
var dist = haversine(lastPoint, { lat: res.latitude, lng: res.longitude });
var dt = (now - lastTime) / 1000;
if (dist > 3 && dt > 0) {
var speed = dist / dt;
speedBucket = getBucket(speed);
}
}
lastPoint = { lat: res.latitude, lng: res.longitude };
lastTime = now;
// 此处调用后端上报接口
sendToServer(res.latitude, res.longitude, speedBucket);
setTimeout(reportLocation, intervalByBucket(speedBucket));
}
});
}
reportLocation();
上面的代码里,sendToServer是你自己封装的上报函数。用setTimeout递归而不是setInterval,好处是每次间隔都是基于最新速度算出来的,不会出现在低速时还按高速周期空转的情况。
另外,微信网页在后台时会挂起JS,所以还要监听visibilitychange,页面隐藏时清空定时器,回到前台重新算一次速度再继续。否则用户切走再回来,可能按着旧的驾车频率在办公室里狂报位置。
后台接收与频率限制的配合策略
前端自适应只是第一步,后端不能全信客户端报上来的频率。恶意用户可以把间隔改成一秒一次刷你接口。所以后台要按用户ID做滑动窗口限流:比如普通用户每分钟最多12条,超过就丢或者返回429。
同时,后台可以结合基站或IP粗定位做交叉验证。如果前端说自己驾车5秒一条,但IP一直在同一个区县不动,那大概率是伪造速度桶,可以直接把该用户降级成静止桶,并写进风控表。这样前端再聪明也绕不过服务端的兜底。
对于上报数据的存储,建议按速度桶分表或者分字段标记。后续做热力图时,静止点权重调低,驾车点用来补轨迹,能明显减小渲染压力。整个方案落地后,我们观察过一个小程序公众号,日均上报量从一亿两千万掉到三千多万,定位投诉反而少了,因为轨迹更跟手了。