React+Antd+百度地图实现轨迹回放功能

本文介绍了如何使用React、Antd和百度地图库实现轨迹回放功能,包括拖动滑块改变图标位置、播放/暂停轨迹移动等。关键在于getOffset方法,用于解决点移动过快的问题。还提出了功能拓展想法,如快进、减速、轨迹颜色变化等。
摘要由CSDN通过智能技术生成

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>
    );
  }
}

  1. getOffset 方法是最重要的方法,这里明白了就能解决很多轨迹移动中点移动过快的问题
  2. 可以继续拓展轨迹回放的功能,比如添加快进或者减速功能,不同路段轨迹颜色不同,点击暂停时从后端获取一些暂停点的一些信息,做一个弹出框,显示计算走了多长距离(这些在本篇文章中并没有体现);

模拟的轨迹数据

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<
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
好的,下面是一个简单的示例代码: 首先安装所需依赖: ```bash npm install react antd axios --save ``` 然后创建一个 `Login` 组件和一个 `Register` 组件,分别用于登录和注册的页面展示。其中,我们使用了 `Form` 和 `Input` 组件来实现表单输入,以及 `Button` 组件来实现按钮的功能。 ```jsx // Login.jsx import React, { useState } from 'react'; import { Form, Input, Button, message } from 'antd'; import axios from 'axios'; const Login = () => { const [loading, setLoading] = useState(false); const onFinish = async (values) => { setLoading(true); try { const response = await axios.post('/api/login', values); message.success(response.data.message); setLoading(false); } catch (error) { message.error(error.response.data.message); setLoading(false); } }; return ( <Form name="login" onFinish={onFinish}> <Form.Item name="email" rules={[{ required: true, message: 'Please input your email!' }]} > <Input placeholder="Email" /> </Form.Item> <Form.Item name="password" rules={[{ required: true, message: 'Please input your password!' }]} > <Input.Password placeholder="Password" /> </Form.Item> <Form.Item> <Button type="primary" htmlType="submit" loading={loading}> Login </Button> </Form.Item> </Form> ); }; export default Login; ``` ```jsx // Register.jsx import React, { useState } from 'react'; import { Form, Input, Button, message } from 'antd'; import axios from 'axios'; const Register = () => { const [loading, setLoading] = useState(false); const onFinish = async (values) => { setLoading(true); try { const response = await axios.post('/api/register', values); message.success(response.data.message); setLoading(false); } catch (error) { message.error(error.response.data.message); setLoading(false); } }; return ( <Form name="register" onFinish={onFinish}> <Form.Item name="name" rules={[{ required: true, message: 'Please input your name!' }]} > <Input placeholder="Name" /> </Form.Item> <Form.Item name="email" rules={[{ required: true, message: 'Please input your email!' }]} > <Input placeholder="Email" /> </Form.Item> <Form.Item name="password" rules={[{ required: true, message: 'Please input your password!' }]} > <Input.Password placeholder="Password" /> </Form.Item> <Form.Item name="confirmPassword" rules={[ { required: true, message: 'Please confirm your password!' }, ({ getFieldValue }) => ({ validator(_, value) { if (!value || getFieldValue('password') === value) { return Promise.resolve(); } return Promise.reject(new Error('The two passwords do not match!')); }, }), ]} > <Input.Password placeholder="Confirm Password" /> </Form.Item> <Form.Item> <Button type="primary" htmlType="submit" loading={loading}> Register </Button> </Form.Item> </Form> ); }; export default Register; ``` 在这里我们使用了 `axios` 来发送登录和注册的请求,需要注意的是,这里的请求地址是 `/api/login` 和 `/api/register`,而不是实际的后端接口地址,根据实际情况进行修改。 最后在需要展示登录和注册功能的页面中引入 `Login` 和 `Register` 组件即可。 ```jsx import React from 'react'; import Login from './Login'; import Register from './Register'; const LoginPage = () => { return ( <div> <h1>Login</h1> <Login /> <hr /> <h1>Register</h1> <Register /> </div> ); }; export default LoginPage; ``` 以上就是一个简单的使用 `react` 和 `antd` 实现登录和注册功能的示例代码,希望对你有帮助。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值