本文详解如何在百度地图JavaScript API中解决Marker移动时的“僵尸跳”现象及有向图标方向错位问题。通过球面坐标转平面坐标,利用线性插值算法在两点间执行多次setPosition实现平滑移动,并结合三角函数计算角度以控制图标旋转,确保车头始终指向行进方向。
在使用百度地图进行实时定位服务开发时,开发者常面临一个视觉体验问题:当获取坐标数据存在延迟或两点间距过大时,覆盖物(Marker)在地图上的位移表现为突兀的跳跃,即俗称的“僵尸跳”,严重影响用户体验。此外,若使用具有方向性的图标(如车辆图标),这种跳跃往往伴随方向错误,例如在立交桥转弯处车头指向后方,引发用户质疑。
为解决上述问题,本文参考百度官方提供的“路书”开源示例,整理出一套实现Marker平滑移动及动态调整车头朝向的解决方案,供开发者参考借鉴。
实现覆盖物在坐标获取间隙的平滑位移
一、实现覆盖物在坐标获取间隙的平滑位移
“僵尸跳”现象的根源在于实时定位数据的获取存在时间差,而覆盖物的坐标更新仅通过简单的setPosition(BMap.Point)方法直接跳转至新坐标,导致视觉上出现停顿和突变。解决思路是在等待新数据的间隙,让覆盖物执行一段平滑的移动动画,而非直接瞬移,通过“小碎步”逐步到达目标点。
具体实现上,该过程可转化为已知起点和终点坐标,在两点连线上多次调用setPosition方法。关键在于控制每次移动的步长,确保在下一次新坐标数据到达前,覆盖物能平滑抵达目标位置。
由于百度地图获取的经纬度坐标属于球面坐标,直接计算两点间的线性路径并不准确,因此需要先将球面坐标转换为平面坐标进行计算。
{BMap.Pixel}= map.getMapType().getProjection().lngLatToPoint(BMap.Point);
随后进行必要的数学运算,具体逻辑可参考路书开源文件的实现方式。
/*
*缓动效果
*初始坐标,目标坐标,当前的步长,总的步长
*@param{BMap.Pixel} initPos 初始平面坐标
*@parm{BMap.Pixel} targetPos 目标平面坐标
*@param{number} 当前帧数
*@param {number} count 总帧数
*/
this.linear = function (initPos, targetPos, currentCount, count) {
var b = initPos, c = targetPos - initPos, t = currentCount,
d = count;
return c * t / d + b;
}
var x = effect(_prvePoint.x, _newPoint.x, currentCount, count),
y = effect(_prvePoint.y, _newPoint.y, currentCount, count);
计算得出的平面坐标pixel(x,y)需再次转换回球面坐标,以便赋值给Marker进行最终定位。相关坐标转换方法可在百度地图类库参考文档中找到(参考链接:http://developer.baidu.com/map/reference/index.php?title=Class:%E6%80%BB%E7%B1%BB/%E5%9C%B0%E5%9B%BE%E7%B1%BB%E5%9E%8B%E7%B1%BB)。
var pos = map.getMapType().getProjection().pointToLngLat(new BMap.Pixel(x, y));
在确定坐标后,需通过修改覆盖物的定位值来实现移动。由于需要多次执行 setPosition,因此需借助 setInterval 定时器来持续更新位置。
me._em._newPointMark.setPosition(pos);
以下是完整的移动方法实现代码。
/**
*小车移动
*@param {Point} prvePoint 开始坐标(PrvePoint)
*@param {Point} newPoint 目标点坐标
*@param {Function} 动画效果
*@return 无返回值
*/
this.Move = function (prvePoint, newPoint, effect, setRotation) {
var me = this,
//当前帧数
currentCount = 0,
//初始坐标
_prvePoint = me._projection.lngLatToPoint(prvePoint),//将球面坐标转换为平面坐标
//获取结束点的(x,y)坐标
_newPoint = me._projection.lngLatToPoint(newPoint),
//两点之间要循环定位的次数
count = me._runTime / me._intervalTimer;
//两点之间匀速移动
me._intervalFlag = setInterval(function () {
//两点之间当前帧数大于总帧数的时候,则说明已经完成移动
if (currentCount >= count) {
clearInterval(me._intervalFlag);
} else {
//动画移动
currentCount++;//计数
var x = effect(_prvePoint.x, _newPoint.x, currentCount, count),
y = effect(_prvePoint.y, _newPoint.y, currentCount, count);
//根据平面坐标转化为球面坐标
var pos = map.getMapType().getProjection().pointToLngLat(new BMap.Pixel(x, y));
//设置marker角度(两点之间的距离车的角度保持一致)
if (currentCount == 1) {
//转换角度 setRotation(prvePoint,newPoint, me._em);
}
//正在移动
me._em._newPointMark.setPosition(pos);
}
}, me._intervalTimer);
me._em._prvePoint = newPoint;
}
代码中的关键参数 count = me._runTime / me._intervalTimer 决定了在两点之间执行“小碎步”移动的总次数,直接影响移动的平滑度和耗时。
count 的具体数值需根据项目实际需求进行计算确定。
控制动画效果主要有两种策略:第一种是通过控制移动数据来计算动画。该方法需根据速度和每次执行的时间间隔计算前进距离,再与两点总距离相除得到执行次数。此方法适用于历史轨迹回放,因为所有坐标已知,只需通过控制速度来调节动画播放快慢,坐标点之间是离散跳变的。
第二种策略是通过控制平滑移动的总时间与每次执行的间隔时间来控制动画。本文示例采用的即为此方法,直接将总时间与间隔时间相除得到执行次数。这种方法更适合实时定位场景,因为接收新定位数据的时间间隔是可控制的,从而可以自主控制平滑移动动画的总时长。
实现车头指向行进方向
二、实现车头指向行进方向
该功能的核心在于动态改变覆盖物的旋转角度,通过调用 me._newPointMark.setRotation(number) 实现。
同样受限于球面坐标,需先将坐标转换为平面坐标以便计算。利用三角函数(如 tan 和 atan)计算两点间的角度值,从而确定车头的旋转角度。这部分逻辑较为固定,可直接参考百度官方示例实现。
/**
*在每个点的真实步骤中设置小车转动的角度
*@param{BMap.Point} curPos 起点
*@param{BMap.Point} targetPos 终点
*/
this.setRotation = function (curPos, targetPos, em)
{
var me = this;
var deg = 0;
curPos = map.pointToPixel(curPos);
targetPos = map.pointToPixel(targetPos);
if (targetPos.x != curPos.x) {
var tan = (targetPos.y - curPos.y) / (targetPos.x - curPos.x),
atan = Math.atan(tan);
deg = atan * 360 / (2 * Math.PI);
if (targetPos.x < curPos.x) {
deg = -deg + 90 + 90;
} else {
deg = -deg;
}
em._newPointMark.setRotation(-deg);
} else {
var disy = targetPos.y - curPos.y;
var bias = 0;
if (disy > 0)
bias = -1
else
bias = 1
em._newPointMark.setRotation(-bias * 90);
}
return;
}
具体的角度计算方法可参考上述代码逻辑。
以上介绍了实现平滑移动及车头朝向控制的基础算法与计算方法。其他业务逻辑代码可根据具体需求进行封装和扩展。
效果图展示如下。


本文内容旨在帮助开发者解决百度地图Marker移动与朝向问题,希望对相关学习或工作有所帮助,欢迎支持。