React+Antd+百度地图实现轨迹回放功能
每一个你不了解的世界,都值得说一句 “Hello World”
本文实现功能参考了百度开源组件路书源码
http://lbsyun.baidu.com/index.php?title=jspopular3.0/openlibrary
前提:项目中已经引入 antd 组件、百度地图js
实现功能有:
1、拖动Silder滑动条的时候可以改变图标位置
2、点击播放图标能沿着地图上面的轨迹线移动
3、点击暂停的时候图标静止
实现效果图
let map = null;
let allPoints = [];
const driveSpeed = 4500;
class Trail extends Component {
constructor(props) {
super(props);
this.state = {
icon: 'caret-right', // caret-right / pause
sliderValue: 0, // 滑动条的值
sliderLen: 0, // 滑动条总长度
};
this.i = 0; // 移动到当前点的索引
this.marker = null; // 当前地图上移动的marker
this.timer = null;
}
componentDidMount() {
// 初始化地图,设置城市和地图级别
map = new BMap.Map(this.map);
map.centerAndZoom(new BMap.Point(116.404, 39.915), 11); // 初始化地图,设置中心点坐标和地图级别
map.enableScrollWheelZoom(true); // 开启鼠标滚轮缩放
// 绘制轨迹
allPoints = trailPoints.map((point) => {
const {
lng, lat } = point;
const tempPoint = this.changeToPoint(lng, lat);
return tempPoint;
});
this.drawLineOnMap(allPoints, '#000000');
map.setViewport(allPoints);
this.setState({
sliderLen: trailPoints.length - 1,
});
}
/**
* 绘制地图上的轨迹
* @param points 轨迹点
* @param color 轨迹颜色
*/
drawLineOnMap = (points, color) => {
map.addOverlay(new BMap.Polyline(points,
{
strokeColor: color }));
};
/**
* 点击播放按钮
*/
handlePlay = () => {
// 地图上必须有点才能点击播放
if (allPoints.length > 0) {
const {
icon } = this.state;
const changeIcon = icon === 'caret-right' ? 'pause' : 'caret-right';
this.setState({
icon: changeIcon,
});
if (icon === 'caret-right') {
this.handleStart();
} else {
this.handleStop();
}
}
};
// 汽车开始运动
handleStart = () => {
// 不是第一次开始,并且小车没有达到终点
if (this.i && this.i < allPoints.length - 1) {
this.moveToNext(this.i += 1);
} else {
// 第一次点击 删除之前的Marker 添加新的Marker
this.addAndClearMarker(allPoints[0]);
this.timer = setTimeout(() => {
this.moveToNext(this.i);
}, 400);
}
};
// 停止运行
handleStop = () => {
this.clearTimer();
};
/**
* 滚动条拖动的事件
* @param value 拖动到的点
*/
onSliderChange = (value) => {
this.i = value;
// 清空上一个点的marker 添加拖动到的位置的marker
this.addAndClearMarker(allPoints[value]);
// 将按钮暂停
this.setState({
icon: 'caret-right',
sliderValue: value,
});
this.clearTimer();
};
/**
* 添加地图上的Marker点
* @param lng 经度
* @param lat 纬度
*/
addMarker = (lng, lat) => new BMap.Marker(new BMap.Point(lng, lat));
/**
* 添加当前位置的marker
* 删除地图上之前添加的marker
*/
addAndClearMarker = (point) => {
// 删除之前的marker
if (this.marker) {
map.removeOverlay(this.marker);
}
// 添加新的marker
const marker = new BMap.Marker(point);
map.addOverlay(marker);
this.marker = marker;
};
/**
* 将地图上的点Point转为坐标轴对象 Pixel
* @param point
*/
changPointToPixel = point => map.getMapType().getProjection().lngLatToPoint(point);
/**
* 移动到下一个点
* @param currentIndex 当前index
*/
moveToNext = (currentIndex) => {
// 改变滑动条的位置
this.setState({
sliderValue: currentIndex,
});
if (currentIndex < allPoints.length - 1) {
this.move(allPoints[currentIndex], allPoints[currentIndex + 1]);
} else {
// 将marker移动到最后一个点上
this.marker.setPosition(allPoints[currentIndex]);
clearTimeout(this.timer);
this.setState({
icon: 'caret-right',
});
}
};
/**
* 移动
* @param currentPoint 当前点
* @param nextPoint 移动到的下一个点
*/
move = (currentPoint, nextPoint) => {
let currentCount = 0;
const timer = 10; // 10ms 执行一次
const step = driveSpeed / (1000 / timer);
const count = Math.round(this.getDistance(currentPoint, nextPoint) / step);
// 初始坐标
const initPos = this.changPointToPixel(currentPoint);
// 结束点的坐标
const targetPost = this.changPointToPixel(nextPoint);
// 如果小于1直接移动到下一点
if (count < 1) {
this.moveToNext(this.i += 1);
return;
}
// 两点之间匀速移动
this.intervalFlag = setInterval(() => {
// 当currentCount > count 时 说明两点间的距离已经走完
if (currentCount > count) {
clearInterval(this.intervalFlag);
if (this.i > allPoints.length) {
return;
}
// 运行下一个点
this.moveToNext(this.i += 1);
} else {
currentCount += 1;
// 计算相邻点的(x, y)
const x = this.getOffset(initPos.x, targetPost.x, currentCount, count);
const y = this.getOffset(initPos.y, targetPost.y, currentCount, count);
const pos = map.getMapType().getProjection().pointToLngLat(new BMap.Pixel(x, y));
this.marker.setPosition(pos);
}
}, timer);
};
/**
* 计算两点之间的距离
* @param startPoint 开始点
* @param endPoint 结束点
* 返回两点之间的距离,保留两位小数
*/
getDistance = (startPoint, endPoint) => map.getDistance(startPoint, endPoint).toFixed(2);
/**
* 计算偏移量——地图下一个点(非后端返回)
* @param initPos 初始点
* @param targetPost 结束点
* @param currentCount 当前的步长点
* @param count 总步长
*/
getOffset = (initPos, targetPost, currentCount, count) => {
const b = initPos;
const c = targetPost - initPos;
const t = currentCount;
const d = count;
return (c * t / d + b);
};
/**
* 将点转为百度地图上的点
* @param lng 经度
* @param lat 纬度
* @returns {BMap.Point} 返回百度地图上的点
*/
changeToPoint = (lng, lat) => new BMap.Point(lng, lat);
/**
* 清空地图上的路线、marker、弹出框
*/
clearMapOverlay = () => {
map.clearOverlays();
};
/**
* 清空页面上的计时器
*/
clearTimer = () => {
if (this.timer) {
clearTimeout(this.timer);
}
if (this.intervalFlag) {
clearInterval(this.intervalFlag);
}
};
render() {
const {
icon,
sliderLen,
sliderValue,
} = this.state;
return (
<Row>
<Col span={
18} offset={
4}>
<div
ref={
(child) => {
this.map = child;
}}
className="trail-box"
/>
<div className="trail-vedio-box">
<Slider
min={
0}
max={
sliderLen}
value={
sliderValue}
onChange={
this.onSliderChange}
/>
<Row>
<Col span={
6} offset={
12}>
<Button
type="danger"
shape="circle"
icon={
icon}
onClick={
this.handlePlay}
/>
</Col>
</Row>
</div>
</Col>
</Row>
);
}
}
- getOffset 方法是最重要的方法,这里明白了就能解决很多轨迹移动中点移动过快的问题
- 可以继续拓展轨迹回放的功能,比如添加快进或者减速功能,不同路段轨迹颜色不同,点击暂停时从后端获取一些暂停点的一些信息,做一个弹出框,显示计算走了多长距离(这些在本篇文章中并没有体现);
模拟的轨迹数据
const trailPoints = [
{
"lng":116.405915,
"lat":39.911887
},
{
"lng":116.405823,
"lat":39.913907
},
{
"lng":116.405813,
"lat":39.914117
},
{
"lng":116.405813,
"lat":39.914117
},
{
"lng":116.405743,
"lat":39.914117
},
{
"lng":116.403984,
"lat":39.914034
},
{
"lng":116.402106,
"lat":39.913978
},
{
"lng":116.402036,
"lat":39.913978
},
{
"lng":116.401917,
"lat":39.913978
},
{
"lng":116.401228,
"lat":39.913945
},
{
"lng":116.401008,
"lat":39.913934
},
{
"lng":116.400599,
"lat":39.913921
},
{
"lng":116.399901,
"lat":39.913897
},
{
"lng":116.399472,
"lat":39.913875
},
{
"lng":116.399292,
"lat":39.913874
},
{
"lng":116.398694,
"lat":39.91385
},
{
"lng":116.398165,
"lat":39.913816
},
{
"lng":116.397667,
"lat":39.913782<