出行类小程序里最核心的一个交互是输入起终点后展示驾车路线,然而很多开发者在实现时只做到了画出一条折线,用户盯着屏幕看了半天也判断不出这条路现在堵不堵、大概什么时候能到。map组件提供的polyline能力虽然能承载轨迹渲染,但要把路况信息和到达时间直观表达出来,还需要结合路线规划接口的数据结构做一层加工。这篇内容将以腾讯位置服务WebServiceAPI为例,逐步说明驾车路线数据从请求、解析到map组件上呈现路况与预计到达时间的完整链路。

路线规划数据的获取与坐标点串解析
调用路线规划接口的第一步是准备请求参数。小程序端通过wx.request发起HTTPS请求,腾讯位置服务的驾车路线规划接口地址为https://apis.map.qq.com/ws/direction/v1/driving/,这里将from和to以经纬度坐标形式传入。经纬度可以使用微信自带接口wx.getLocation获取当前定位,再交由页面上的起终点选择组件设置目的地坐标。关键点在于请求参数中务必携带key,该key从小程序后台的腾讯位置服务控制台申请,用于标识调用方身份。
接口返回的数据结构中最核心的字段位于result.routes数组。每个route对象中包含一个polyline字段,它是一串经过编码的坐标点集合。出于传输体积考虑,腾讯位置服务对坐标点串做了压缩编码,直接拿来画线肯定不行。开发者需要先对polyline做解码,还原出完整的经纬度点数组,才能交给map组件的polyline属性渲染。若对解码后的数组不做任何抽稀处理,在路线较长时,polyline点数量可能达到数百上千个,直接赋值给polyline虽然能显示,但数据量偏大时会增加视图渲染压力。
实际处理时,可以使用腾讯位置服务提供的JavaScript SDK,也可以自行编写解码函数。下面给出一个完整的路线请求与解析示例,代码中的parsePolylineToPoints为简易解码函数,将该函数返回的点数组直接用于map组件绘制轨迹即可。若路线点过于密集,可以每隔一个点取一个进行抽稀处理,例如通过filter方法按步长筛选。
Page({
data: {
polyline: [],
distanceText: '',
durationText: ''
},
planRoute: function (fromLat, fromLng, toLat, toLng) {
var that = this;
wx.request({
url: 'https://apis.map.qq.com/ws/direction/v1/driving/',
data: {
from: fromLat + ',' + fromLng,
to: toLat + ',' + toLng,
key: '您的APIKEY'
},
success: function (res) {
if (res.data.status !== 0) {
console.log('路线规划失败', res.data.message);
return;
}
var route = res.data.result.routes[0];
var points = that.decodePolyline(route.polyline);
that.setData({
polyline: [{
points: points,
color: '#4682B4',
width: 6,
arrowLine: true
}],
distanceText: route.distance + '米',
durationText: route.duration + '秒'
});
}
});
},
decodePolyline: function (encoded) {
var points = [];
var index = 0;
var lat = 0;
var lng = 0;
while (index < encoded.length) {
var b = 0;
var shift = 0;
var result = 0;
do {
b = encoded.charAt(index++).charCodeAt(0) - 63;
result |= (b & 0x1f) << shift;
shift += 5;
} while (b >= 0x20);
var deltaLat = (result & 1) ? ~(result >> 1) : (result >> 1);
lat += deltaLat;
shift = 0;
result = 0;
do {
b = encoded.charAt(index++).charCodeAt(0) - 63;
result |= (b & 0x1f) << shift;
shift += 5;
} while (b >= 0x20);
var deltaLng = (result & 1) ? ~(result >> 1) : (result >> 1);
lng += deltaLng;
points.push({
latitude: lat / 1000000,
longitude: lng / 1000000
});
}
return points;
}
});
decodePolyline函数的核心逻辑是对编码字符串按位读取,每次读取5个比特位,直到最高位为0为止。奇数表示负数,偶数表示正数。每次解码得到纬度增量和经度增量后,累加到前一个坐标上。解码完成后需要将坐标值除以1000000,因为编码时坐标进行了百万倍缩放。这个环节处理正确与否直接影响后续所有路况展示,如果polyline解析出来是一条乱飞的线,大概率就是解码逻辑有误。
叠加实时路况:按路段拆分polyline并以颜色标注
实时路况展示的基本思路是:将完整路线按照路况数据拆分成多个分段,每一段使用对应的颜色渲染。例如畅通路段用绿色,缓行路段用黄色,拥堵路段用红色。在动手操作前,需要了解路况数据的获取方式。腾讯位置服务的驾车路线规划接口返回的routes数组中,每个route对象含有一个traffic_condition字段,该字段是一个数组,其中包含该条路线的整体路况状态level和文字描述tips。但这里的level仅是整条线路的汇总状态,无法支撑细粒度的分段上色。
要实现分路段上色,需要获取更细致的路况分段信息。一种常见做法是后端服务在请求路线规划的同时,再请求腾讯位置服务的路况接口,或直接从路线规划结果的steps中提取每个step的路况状态。每个step包含一条道路的名称、方向、距离以及traffic_condition。如果step中的traffic_condition过于粗略,还可以进一步把step的polyline按照交通状态拆开。以下是为polyline添加分段颜色的实现示例,每一段拥有独立坐标集合与颜色。
function buildTrafficPolyline(stepList) {
var polylines = [];
var colorMap = {
0: '#2ECC40', // 畅通
1: '#FFDC00', // 缓行
2: '#FF851B', // 慢速
3: '#FF4136', // 拥堵
4: '#B10DC9' // 严重拥堵
};
stepList.forEach(function (step) {
var decodedPoints = decodePolyline(step.polyline);
var level = step.traffic_condition.level;
// 避免连续同颜色的step产生多余过渡,直接追加为独立line段
polylines.push({
points: decodedPoints,
color: colorMap[level] || '#2ECC40',
width: 6,
arrowLine: false
});
});
return polylines;
}
上面的色彩映射表遵循了大众对路况颜色的认知习惯,畅通绿色、拥堵红色。如果希望视觉效果更贴近地图产品,可以单独使用腾讯位置服务提供的路况图层,但那种方式无法替换map组件里的polyline样式,因此这里推荐自定义分段上色方式。这种方式还有另一个好处,就是分段线条上可以单独绑定点击事件,用户点击某一段时弹出该路段的距离与预计耗时。
另一个值得注意的性能问题是,分段较多时页面上的polyline数组会变得很长。渲染层需要遍历每一个polyline对象并生成对应的canvas指令或native地图图层,过多的polyline对象在低端机型上容易出现拖动地图掉帧。推荐的优化方式是按路况等级合并相邻的polyline:若前后两段路况颜色一致,就可以将这两段坐标合并为一个polyline,只有当路况颜色变化时才开启新的分段。这能在不影响视觉效果的前提下,有效降低polyline的总数量。
预计到达时间显示与前端动态修正
路线规划接口返回的duration字段是理论行驶时间,其计算一般依据道路限速和距离,并没有充分把实时拥堵纳入考量,直接把这个数字展示给用户,极有可能出现明明显示30分钟却在路上堵了1个小时的情况。为了提升预估时间的可信度,需要将路况数据叠加到剩余时间的计算中。
修正思路是引入路况系数。根据前一步得到的各段路况level,为每段路分配一个时间放大倍数。例如畅通路段系数为1.0,缓行路段系数为1.4,拥堵路段系数为2.0,严重拥堵系数为2.8。将每段的基础行驶时间乘以对应系数后再累加,得到修正后的预计到达时间。具体计算时,需要将每一段路的distance除以平均速度得到一个基准时间,再乘上系数。
function calcEtaWithTraffic(steps) {
var totalSeconds = 0;
var levelFactor = {
0: 1.0,
1: 1.4,
2: 2.0,
3: 2.8,
4: 3.5
};
steps.forEach(function (step) {
var distanceMeter = step.distance;
var level = step.traffic_condition.level;
// 畅通路段的平均时速按40km/h估算,约11.1m/s
var baseSpeed = 11.1;
var factor = levelFactor[level] || 1.0;
var stepSeconds = distanceMeter / baseSpeed * factor;
totalSeconds += stepSeconds;
});
return totalSeconds;
}
这里有两点需要特别说明。第一,基础平均速度选用40km/h是考虑到城市道路的混合工况,不同城市、不同时段差异显著,若后端有能力拿到历史路况数据,可以根据时段动态调整baseSpeed。第二,若用户已经在行驶途中,应该将未行驶的剩余距离代入计算,而不是对全程重新估算。判断用户是否已驶过某段路,可利用map组件的marker移动轨迹或对比当前坐标与路线坐标点的距离,当坐标距某一路段终点小于20米时即可认为该段已经通过。
路况与预计到达时间的定时联动刷新
道路上的交通状况是动态变化的,如果只在路线规划成功后绘制一次路况颜色,用户停留页面时间较长时看到的就是陈旧数据。因此必须建立一套刷新机制,让路况颜色和预计到达时间每隔一段时间自动更新。实现刷新方案时,小程序需要调用腾讯位置服务的路况查询接口,或再次调用路线规划接口重新获取路况信息。考虑到接口配额与设备耗电,不建议高频轮询。
推荐策略是:页面onShow时立即刷新一次,随后每隔3分钟刷新一次。如果小程序被切换到后台,则暂停计时器。对于行驶中场景,可以在wx.onLocationChange回调中判断用户位置变化是否超过500米,若超过则主动触发一次路况刷新。这样既保证了数据的相对新鲜,又不至于在静止状态下频繁请求。一个带清理逻辑的刷新实现如下。
Page({
data: {
trafficPolyline: [],
etaText: ''
},
onShow: function () {
this.refreshTrafficData();
this.startPolling();
},
onHide: function () {
this.stopPolling();
},
startPolling: function () {
var that = this;
this.pollingTimer = setInterval(function () {
that.refreshTrafficData();
}, 3 * 60 * 1000);
},
stopPolling: function () {
if (this.pollingTimer) {
clearInterval(this.pollingTimer);
this.pollingTimer = null;
}
},
refreshTrafficData: function () {
var that = this;
wx.request({
url: 'https://apis.map.qq.com/ws/direction/v1/driving/',
data: {
from: that.data.fromCoord,
to: that.data.toCoord,
key: '您的APIKEY'
},
success: function (res) {
var route = res.data.result.routes[0];
var newPolylines = buildTrafficPolyline(route.steps);
var totalSeconds = calcEtaWithTraffic(route.steps);
that.setData({
trafficPolyline: newPolylines,
etaText: formatEta(totalSeconds)
});
}
});
}
});
function formatEta(seconds) {
if (seconds < 60) {
return Math.round(seconds) + '秒';
}
var minutes = Math.round(seconds / 60);
if (minutes < 60) {
return minutes + '分钟';
}
var hours = Math.floor(minutes / 60);
var remainMinutes = minutes % 60;
return hours + '小时' + (remainMinutes > 0 ? remainMinutes + '分钟' : '');
}
轮询刷新需要注意生命周期管理,onHide时清除定时器可以避免不必要的接口调用。当用户重新进入页面时,onShow会再次启动轮询,从而完成一次完整的更新循环。在页面退出时(onUnload),也应调用clearInterval防止定时器泄漏。
从功能到体验的进一步完善
上述方案已经把路径展示、路况颜色和预计到达时间串联成一个动态更新的闭环,但在实际项目中还可以继续优化。比如在导航开始时,根据实时路况为不同路段分配不同建议速度,并在地图上用文字标签展示预计剩余时间,随着位置更新持续刷新。这种动态的数字变化能显著加强用户对路线规划的信任感。
此外,可以结合wx.createMapContext实现路线周边路况的聚合提示。当用户缩放地图到较大视野时,保留主路线的同时,对周边主要道路的路况状态做半透明色带叠加,这需要另一个图层来处理。若暂时不打算引入自定义图层,也可以在map组件下方放置一个简洁的卡片,显示总距离、预计到达时间以及当前路况文字描述。这样不干扰地图操作,也能够把最关键的信息直观呈现给用户。
最后需要留意的是腾讯位置服务API的配额和商用授权边界。上线前务必确认所使用的WebServiceAPI已经开通对应的服务,并且域名已添加至小程序后台request合法域名。若流量较大,建议将路线规划请求放到后端代理,前端从小程序业务后台获取数据,避免密钥暴露在客户端引来安全风险。