微信公众号里的地理位置能力通常依赖JS-SDK的wx.getLocation接口。业务方拿到经纬度后可以用于签到、巡检、配送轨迹、门店推荐等不同用途。虽然接口本身用法不复杂,但真正上线后容易遇到两类问题:一是固定间隔上报导致接口调用量迅速见顶,二是精度设置过高使部分低端机型出现定位超时。要解决这些问题,不能只把上报逻辑写死,而需要根据运行场景动态选择策略。本文讨论一个面向公众号H5页面的上报策略优化工具,它把策略配置、位置过滤和队列发送拆开,便于按业务场景调整。

场景差异决定上报策略不能一刀切
地理位置上报并不是一种通用逻辑。巡检业务可能要每10秒记录一次轨迹,签到业务可以在用户点击时只取一次坐标,门店推荐业务只需要城市级别位置。若把这些场景用同一套参数处理,轻则浪费调用配额,重则因频率过高被微信侧限制。以微信JS-SDK为例,getLocation接口对调用频率有一定约束,且每次调用可能唤起定位权限提示或增加耗电,页面在前台和后台时表现也不一致。
场景差异可以从四个维度描述:精度、频率、最小位移和前后台策略。精度维度上,高精度坐标适合配送和巡检轨迹,低精度坐标适合商圈推荐。频率维度上,连续运动场景需要定时采集,但间隔不能短于业务容忍值。最小位移维度上,静止用户不需要重复上报,可以用距离阈值过滤。前后台维度上,页面切后台后可以暂停或降频,回到前台再恢复。优化工具要做的就是把这几项抽成配置,让业务方不必修改代码。
举例来说,一个门店巡检场景会在上午执行,人员停留时间较长。此时可以配置高精度但低频,最小位移阈值为15米,后台暂停。一个同城配送场景则需要中精度、高频、最小位移阈值为5米,后台保持采集。若反过来配置,巡检会产生大量冗余点,配送轨迹会显得稀疏。可见同样的wx.getLocation调用,策略参数不同,业务价值差别很大。
策略工具的核心模块与配置模型
该工具在结构上可以拆成四个模块:策略配置中心、位置采集器、过滤队列和上报通道。策略配置中心保存不同场景的JSON配置,位置采集器负责调用wx.getLocation并处理权限失败,过滤队列根据距离和时间判断是否丢弃点,上报通道将符合条件的坐标按批次发送到服务端。这样拆分后,业务页面只关心启动和停止,不直接接触底层参数。
策略配置可以用一个对象表达。比如巡逻场景的配置如下:
var patrolStrategy = {
scene: 'patrol',
coordinateType: 'gcj02',
interval: 10000,
minDistance: 15,
maxDistance: 500,
backgroundPause: true,
batchSize: 5,
flushInterval: 30000
};
这里interval表示前台定时采集间隔,minDistance表示两次上报之间的最小位移,maxDistance用于防止GPS漂移造成异常点。backgroundPause控制页面切后台时是否暂停。batchSize和flushInterval用于批量发送,避免每个坐标都发起一次HTTP请求。其他场景可以复用这个结构,只在配置中心修改数值。
配置模型中还需要一个默认策略。当页面没有携带场景标识或场景未配置时,回退到保守策略,例如中精度、60秒间隔、最小位移10米。默认策略的意义在于防止误配置导致接口调用失控。工具初始化时会先读取默认策略,再尝试加载具体场景配置。
关键代码实现:位置采集、过滤与批量上报
位置采集器要处理微信JS-SDK的异步回调。wx.getLocation支持type参数,wgs84返回GPS坐标,gcj02返回国测局坐标。国内地图服务一般使用gcj02,减少后续偏移。以下是一个基础封装:
function startLocationReporter(strategy) {
var lastLocation = null;
var queue = [];
var timer = null;
function collect() {
wx.getLocation({
type: strategy.coordinateType || 'gcj02',
success: function(res) {
if (!lastLocation || shouldKeep(lastLocation, res, strategy)) {
lastLocation = {
latitude: res.latitude,
longitude: res.longitude,
accuracy: res.accuracy,
timestamp: Date.now()
};
queue.push(lastLocation);
if (queue.length >= strategy.batchSize) {
flush(queue);
queue = [];
}
}
},
fail: function(err) {
if (err && err.errMsg && err.errMsg.indexOf('auth') > -1) {
stop();
}
}
});
}
timer = setInterval(collect, strategy.interval || 30000);
collect();
}
上面的代码里,shouldKeep用于距离过滤,flush负责批量上报。定时器间隔来自策略配置,本地队列达到batchSize后立即发送,同时可以再设置一个最大等待时间flushInterval,避免数据长时间滞留内存。如果用户拒绝授权,回调中的errMsg会包含auth相关信息,此时停止采集比反复重试更合理。
距离过滤需要计算两个坐标点之间的距离。Haversine公式适合短距离计算,实现也简单。示例:
function shouldKeep(prev, curr, strategy) {
var distance = getDistance(prev.latitude, prev.longitude, curr.latitude, curr.longitude);
if (distance < strategy.minDistance) {
return false;
}
if (distance > strategy.maxDistance) {
return false;
}
return true;
}
function getDistance(lat1, lng1, lat2, lng2) {
var R = 6371000;
var rad = Math.PI / 180;
var dLat = (lat2 - lat1) * rad;
var dLng = (lng2 - lng1) * rad;
var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(lat1 * rad) * Math.cos(lat2 * rad) *
Math.sin(dLng / 2) * Math.sin(dLng / 2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c;
}
这段逻辑的重点不在公式复杂度,而在是否将阈值做成可配置项。写死5米或10米都会限制场景适配能力。maxDistance则能过滤GPS漂移产生的远距离跳点,尤其是高楼层或隧道附近,漂移点可能达到数百米甚至上千米,服务端若不做处理会污染轨迹。
调优实践与常见误区
上线前建议先用小流量验证策略参数。可以通过后台记录实际间隔、丢弃率和批量发送次数,观察服务端接收到的坐标点是否与业务预期匹配。例如巡检场景如果丢弃率超过80%,可能说明minDistance设置过大,或者用户实际走动距离很小。配送场景如果出现折线跳变,说明maxDistance没有有效过滤漂移点,可以适当收紧阈值。
常见误区有两个。一是把高精度等同于高价值,所有场景都使用高精度定位。高精度定位不仅耗电更多,在部分安卓机型上定位时间更长,用户等待感明显。公众号页面通常没有独立App那样的定位优化空间,更需要控制调用成本。二是忽略页面生命周期。很多页面在切后台后仍然通过定时器持续调用getLocation,既无业务意义,也容易造成服务端数据冗余。工具中应监听visibilitychange事件,根据backgroundPause配置暂停或降低频率。
批量上报的时间窗口也需要与业务实时性匹配。实时性强的配送轨迹可以将flushInterval设为10秒,而对实时性要求不高的巡检轨迹可以设为30秒或更久。此外,本地队列在页面卸载前应尝试发送一次,使用navigator.sendBeacon可以在页面关闭时尽量送达,但不保证所有浏览器都支持。微信内置浏览器对sendBeacon的支持情况需要实际验证。
从效果上看,配置化策略可以把地理位置接口调用量降低30%到60%,具体幅度取决于业务场景。更重要的是,后续新增业务时不用改动采集代码,只需在配置中心添加场景参数。这样开发和运维都能更清晰地知道每个页面的上报行为,避免出现某些页面无节制轮询的隐患。